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:
1 parent
cac911f08e
commit
08ff214ea6
919 files changed
+45652
-52097
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import React, { PureComponent, memo } from 'react';
|
||||
import { DragLayer } from 'react-dnd';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ThumbnailEntry } from './ThumbnailEntry';
|
||||
import './DragPreview.styl';
|
||||
|
||||
let subscribedToOffsetChange = false;
|
||||
let dragPreviewRef = null;
|
||||
|
||||
const collector = monitor => {
|
||||
if (!subscribedToOffsetChange) {
|
||||
monitor.subscribeToOffsetChange(onOffsetChange(monitor));
|
||||
subscribedToOffsetChange = true;
|
||||
}
|
||||
|
||||
if (dragPreviewRef) {
|
||||
const offset =
|
||||
monitor.getSourceClientOffset() || monitor.getInitialSourceClientOffset();
|
||||
|
||||
if (offset) {
|
||||
const transform = `translate(${offset.x}px, ${offset.y}px)`;
|
||||
dragPreviewRef.style['transform'] = transform;
|
||||
dragPreviewRef.style['-webkit-transform'] = transform;
|
||||
}
|
||||
}
|
||||
|
||||
const item = monitor.getItem();
|
||||
let newItem = {};
|
||||
if (item) {
|
||||
newItem = {
|
||||
active: item.active,
|
||||
altImageText: item.altImageText,
|
||||
id: item.id,
|
||||
imageSrc: item.imageSrc,
|
||||
imageId: item.imageId,
|
||||
instanceNumber: item.instanceNumber,
|
||||
error: item.error,
|
||||
numImageFrames: item.numImageFrames,
|
||||
seriesDescription: item.seriesDescription,
|
||||
seriesNumber: item.seriesNumber,
|
||||
stackPercentComplete: item.stackPercentComplete,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...newItem,
|
||||
isDragging: monitor.isDragging(),
|
||||
};
|
||||
};
|
||||
|
||||
const onOffsetChange = monitor => () => {
|
||||
if (!dragPreviewRef) return;
|
||||
|
||||
const offset =
|
||||
monitor.getSourceClientOffset() || monitor.getInitialSourceClientOffset();
|
||||
if (!offset) return;
|
||||
|
||||
const transform = `translate(${offset.x}px, ${offset.y}px)`;
|
||||
dragPreviewRef.style['transform'] = transform;
|
||||
dragPreviewRef.style['-webkit-transform'] = transform;
|
||||
};
|
||||
|
||||
const updateRef = ref => {
|
||||
dragPreviewRef = ref;
|
||||
};
|
||||
|
||||
class DragPreview extends PureComponent {
|
||||
render() {
|
||||
const { isDragging } = this.props;
|
||||
if (!isDragging) return null;
|
||||
return (
|
||||
<div className="DragPreview">
|
||||
<div className="source-preview" ref={updateRef}>
|
||||
<ThumbnailEntry {...this.props} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DragPreview.propTypes = {
|
||||
isDragging: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default DragLayer(collector)(memo(DragPreview));
|
||||
@@ -0,0 +1,14 @@
|
||||
.DragPreview
|
||||
|
||||
.source-preview {
|
||||
border-radius: 5px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
position: fixed;
|
||||
opacity: 0.5;
|
||||
z-index: 9999;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transition: none;
|
||||
pointer-events: none;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
.ExampleDropTarget {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
min-height: 100px;
|
||||
|
||||
border: 2px dashed black;
|
||||
padding: 20px;
|
||||
transition: 0.2s all ease;
|
||||
}
|
||||
|
||||
.ExampleDropTarget h4,
|
||||
.ExampleDropTarger p {
|
||||
text-align: center;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.ExampleDropTarget.can-drop {
|
||||
border: 2px dashed #20a5d6;
|
||||
background-color: #fafafa;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.ExampleDropTarget.hovered {
|
||||
border: 2px dashed #20a5d6;
|
||||
background-color: black;
|
||||
color: #91b9cd;
|
||||
cursor: copy;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import React, { Component } from 'react';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
import PropTypes from 'prop-types';
|
||||
import './ExampleDropTarget.css';
|
||||
|
||||
// Drag sources and drop targets only interact
|
||||
// if they have the same string type.
|
||||
const Types = {
|
||||
THUMBNAIL: 'thumbnail',
|
||||
};
|
||||
|
||||
const divTarget = {
|
||||
drop(props, monitor, component) {
|
||||
// Note: For this example we use setState, but in
|
||||
// OHIF we will update the redux store instead
|
||||
const item = monitor.getItem();
|
||||
|
||||
component.setState({
|
||||
item: {
|
||||
id: item.id,
|
||||
seriesDescription: item.seriesDescription,
|
||||
},
|
||||
});
|
||||
return { id: 'ExampleDropTarget' };
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Find out why we can't move this into the Example app instead.
|
||||
// It looks like the context isn't properly shared.
|
||||
class CustomDropTarget extends Component {
|
||||
static className = 'ExampleDropTarget';
|
||||
|
||||
state = {
|
||||
item: null,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isOver: false,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
connectDropTarget: PropTypes.func.isRequired,
|
||||
canDrop: PropTypes.bool.isRequired,
|
||||
isOver: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { canDrop, isOver, connectDropTarget } = this.props;
|
||||
const isActive = canDrop && isOver;
|
||||
|
||||
let className = ExampleDropTarget.className;
|
||||
|
||||
if (isActive) {
|
||||
className += ' hovered';
|
||||
} else if (canDrop) {
|
||||
className += ' can-drop';
|
||||
}
|
||||
|
||||
return connectDropTarget(
|
||||
<div className={className}>
|
||||
<h4>
|
||||
{isActive
|
||||
? 'Release to drop'
|
||||
: 'Drag / Drop something from the Study Browser here'}
|
||||
</h4>
|
||||
<p className="study-drop-results">
|
||||
{this.state.item && JSON.stringify(this.state.item)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const collect = (connect, monitor) => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
});
|
||||
|
||||
const ExampleDropTarget = DropTarget(Types.THUMBNAIL, divTarget, collect)(
|
||||
CustomDropTarget
|
||||
);
|
||||
|
||||
export { ExampleDropTarget };
|
||||
@@ -0,0 +1,136 @@
|
||||
import './ImageThumbnail.styl';
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import ViewportErrorIndicator from '../../viewer/ViewportErrorIndicator';
|
||||
import ViewportLoadingIndicator from '../../viewer/ViewportLoadingIndicator';
|
||||
|
||||
// TODO: How should we have this component depend on Cornerstone?
|
||||
// - Passed in as a prop?
|
||||
// - Set as external dependency?
|
||||
// - Pass in the entire load and render function as a prop?
|
||||
//import cornerstone from 'cornerstone-core';
|
||||
|
||||
/**
|
||||
* Asynchronous wrapper around Cornerstone's renderToCanvas method.
|
||||
*
|
||||
* @param {HTMLElement} canvasElement An HTML <canvas> element
|
||||
* @param {Image} image A Cornerstone Image
|
||||
*
|
||||
* @return {Promise} A promise tracking the progress of the rendering. Resolves empty.
|
||||
*/
|
||||
function renderAsync(canvasElement, image) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
cornerstone.renderToCanvas(canvasElement, image);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default class ImageThumbnail extends PureComponent {
|
||||
static propTypes = {
|
||||
imageSrc: PropTypes.string,
|
||||
imageId: PropTypes.string,
|
||||
error: PropTypes.bool.isRequired,
|
||||
width: PropTypes.number.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
stackPercentComplete: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
error: false,
|
||||
stackPercentComplete: 0,
|
||||
width: 217,
|
||||
height: 123,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.canvas = React.createRef();
|
||||
|
||||
const renderIntoCanvas = this.props.imageId && !this.props.imageSrc;
|
||||
|
||||
this.state = {
|
||||
loading: renderIntoCanvas,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const renderIntoCanvas = this.props.imageId && !this.props.imageSrc;
|
||||
|
||||
if (renderIntoCanvas) {
|
||||
const { imageId } = this.props;
|
||||
const canvas = this.canvas.current;
|
||||
|
||||
cornerstone.loadAndCacheImage(imageId).then(
|
||||
image => {
|
||||
renderAsync(canvas, image).then(
|
||||
() => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
});
|
||||
},
|
||||
error => {
|
||||
// TODO: Set state?
|
||||
throw new Error(error);
|
||||
}
|
||||
);
|
||||
},
|
||||
error => {
|
||||
// TODO: Set state?
|
||||
throw new Error(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let loadingOrError;
|
||||
if (this.props.error) {
|
||||
loadingOrError = <ViewportErrorIndicator />;
|
||||
} else if (this.state.loading) {
|
||||
loadingOrError = <ViewportLoadingIndicator />;
|
||||
}
|
||||
|
||||
const showStackLoadingProgressBar =
|
||||
this.props.stackPercentComplete !== undefined;
|
||||
|
||||
const renderIntoCanvas = this.props.imageId && !this.props.imageSrc;
|
||||
|
||||
return (
|
||||
<div className="ImageThumbnail">
|
||||
<div className="image-thumbnail-canvas">
|
||||
{renderIntoCanvas ? (
|
||||
<canvas
|
||||
ref={this.canvas}
|
||||
width={this.props.width}
|
||||
height={this.props.height}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
className="static-image"
|
||||
src={this.props.imageSrc}
|
||||
//width={this.props.width}
|
||||
height={this.props.height}
|
||||
alt={''}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{loadingOrError}
|
||||
{showStackLoadingProgressBar && (
|
||||
<div className="image-thumbnail-progress-bar">
|
||||
<div
|
||||
className="image-thumbnail-progress-bar-inner"
|
||||
style={{ width: `${this.props.stackPercentComplete}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
:root {
|
||||
--text-secondary-color: #91B9CD;
|
||||
--active-color: #20A5D6;
|
||||
--primary-background-color: black;
|
||||
--ui-border-color-dark: #3C5D80
|
||||
--sidebar-transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.ThumbnailEntry.active .ImageThumbnail
|
||||
border-color: var(--active-color);
|
||||
box-shadow: none
|
||||
transition: var(--sidebar-transition);
|
||||
|
||||
.ImageThumbnail
|
||||
background-color: var(--primary-background-color);
|
||||
box-shadow: inset 0 0 0 1px var(--ui-border-color-dark);
|
||||
border: 5px solid transparent
|
||||
border-radius: 12px
|
||||
height: 135px
|
||||
margin: 0 auto
|
||||
padding 5px
|
||||
position: relative
|
||||
transition: var(--sidebar-transition);
|
||||
width: 217px
|
||||
-moz-background-clip: padding
|
||||
-webkit-background-clip: padding
|
||||
background-clip: padding-box
|
||||
|
||||
.image-thumbnail-canvas
|
||||
height: 100%
|
||||
overflow: hidden
|
||||
display: flex
|
||||
justify-content: center
|
||||
|
||||
img
|
||||
-webkit-user-drag: none
|
||||
pointer-events: none
|
||||
|
||||
|
||||
.thumbnailLoadingIndicator
|
||||
display: none
|
||||
pointer-events: none
|
||||
color: var(--text-secondary-color);
|
||||
height: 20px
|
||||
width: 100%
|
||||
top: 0
|
||||
left: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
margin: auto
|
||||
position: absolute
|
||||
|
||||
&.d-block
|
||||
display: block;
|
||||
|
||||
p
|
||||
text-align: center
|
||||
font-size: 10pt
|
||||
|
||||
.image-thumbnail-progress-bar
|
||||
position: relative
|
||||
width: 100%
|
||||
height: 3px
|
||||
top: -5px
|
||||
|
||||
.image-thumbnail-progress-bar-inner
|
||||
height: 100%
|
||||
width: 0
|
||||
border-radius: 5px
|
||||
background-color: var(--active-color);
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ThumbnailEntry } from './ThumbnailEntry';
|
||||
import ThumbnailEntryDragSource from './ThumbnailEntryDragSource.js';
|
||||
import './StudyBrowser.styl';
|
||||
|
||||
class StudyBrowser extends Component {
|
||||
static defaultProps = {
|
||||
studies: [],
|
||||
supportsDragAndDrop: true,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
studies: PropTypes.array.isRequired,
|
||||
supportsDragAndDrop: PropTypes.bool.isRequired,
|
||||
onThumbnailClick: PropTypes.func,
|
||||
onThumbnailDoubleClick: PropTypes.func,
|
||||
};
|
||||
|
||||
render() {
|
||||
const studies = this.props.studies;
|
||||
|
||||
const thumbnails = studies.map((study, studyIndex) => {
|
||||
return study.thumbnails.map((thumb, thumbIndex) => {
|
||||
if (this.props.supportsDragAndDrop) {
|
||||
return (
|
||||
<ThumbnailEntryDragSource
|
||||
key={`${studyIndex}_${thumbIndex}`}
|
||||
{...study}
|
||||
{...thumb}
|
||||
id={`${studyIndex}_${thumbIndex}`}
|
||||
onClick={this.props.onThumbnailClick}
|
||||
onDoubleClick={this.props.onThumbnailDoubleClick}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="ThumbnailEntryContainer">
|
||||
<ThumbnailEntry
|
||||
key={`${studyIndex}_${thumbIndex}`}
|
||||
{...study}
|
||||
{...thumb}
|
||||
id={`${studyIndex}_${thumbIndex}`}
|
||||
onClick={this.props.onThumbnailClick}
|
||||
onDoubleClick={this.props.onThumbnailDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const components = thumbnails.flat();
|
||||
return (
|
||||
<div className="StudyBrowser">
|
||||
<div className="scrollable-study-thumbnails">{components}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { StudyBrowser };
|
||||
@@ -0,0 +1,40 @@
|
||||
.StudyBrowser
|
||||
float: left
|
||||
height: 100%
|
||||
width: 100%
|
||||
overflow: hidden
|
||||
background-color: black
|
||||
padding-bottom: 20px
|
||||
padding-top: 10px
|
||||
|
||||
.scrollable-study-thumbnails
|
||||
height: 100%
|
||||
overflow-y: auto
|
||||
overflow-x: hidden
|
||||
padding-bottom: 50px
|
||||
padding-right: 16px
|
||||
padding-left: 4px
|
||||
margin-right: -16px
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&::-webkit-scrollbar
|
||||
display: none
|
||||
|
||||
.ThumbnailEntryContainer
|
||||
margin: 0 auto;
|
||||
padding-bottom: 1.5rem;
|
||||
|
||||
.noselect
|
||||
-moz-user-select: none; /* Firefox */
|
||||
-ms-user-select: none; /* Internet Explorer */
|
||||
-khtml-user-select: none; /* KHTML browsers (e.g. Konqueror) */
|
||||
-webkit-user-select: none; /* Chrome, Safari, and Opera */
|
||||
-webkit-touch-callout: none; /* Disable Android and iOS callouts*/
|
||||
|
||||
|
||||
.draggable
|
||||
cursor: copy
|
||||
cursor: -webkit-grab
|
||||
cursor: -moz-grab
|
||||
@@ -0,0 +1,113 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImageThumbnail from './ImageThumbnail';
|
||||
import './ThumbnailEntry.styl';
|
||||
import classnames from 'classnames';
|
||||
|
||||
class ThumbnailEntry extends Component {
|
||||
static defaultProps = {
|
||||
active: false,
|
||||
error: false,
|
||||
stackPercentComplete: 0,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
imageSrc: PropTypes.string,
|
||||
imageId: PropTypes.string,
|
||||
error: PropTypes.bool.isRequired,
|
||||
active: PropTypes.bool.isRequired,
|
||||
stackPercentComplete: PropTypes.number,
|
||||
/**
|
||||
altImageText will be used when no imageId or imageSrc is provided.
|
||||
It will be displayed inside the <div>. This is useful when it is difficult
|
||||
to make a preview for a type of DICOM series (e.g. DICOM-SR)
|
||||
*/
|
||||
altImageText: PropTypes.string,
|
||||
seriesDescription: PropTypes.string,
|
||||
seriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
instanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
numImageFrames: PropTypes.number,
|
||||
onDoubleClick: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
render() {
|
||||
const hasInstanceNumber = this.props.instanceNumber !== undefined;
|
||||
|
||||
let className = classnames('ThumbnailEntry noselect', {
|
||||
active: this.props.active,
|
||||
});
|
||||
const infoOnly = false;
|
||||
|
||||
let contents = null;
|
||||
if (this.props.imageSrc || this.props.imageId) {
|
||||
contents = (
|
||||
<div className="p-x-1">
|
||||
<ImageThumbnail
|
||||
imageSrc={this.props.imageSrc}
|
||||
imageId={this.props.imageId}
|
||||
error={this.props.error}
|
||||
stackPercentComplete={this.props.stackPercentComplete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.altImageText) {
|
||||
contents = (
|
||||
<div className={'alt-image-text p-x-1'}>
|
||||
<h1>{this.props.altImageText}</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className}
|
||||
onClick={this.onClick}
|
||||
onDoubleClick={this.onDoubleClick}
|
||||
onMouseDown={this.onMouseDown}
|
||||
>
|
||||
{contents}
|
||||
<div
|
||||
className={infoOnly ? 'series-details info-only' : 'series-details'}
|
||||
>
|
||||
<div className="series-description">
|
||||
{this.props.seriesDescription}
|
||||
</div>
|
||||
<div className="series-information">
|
||||
<div className="item item-series clearfix">
|
||||
<div className="icon">S:</div>
|
||||
<div className="value">{this.props.seriesNumber}</div>
|
||||
</div>
|
||||
{hasInstanceNumber && (
|
||||
<div className="item item-series clearfix">
|
||||
<div className="icon">I:</div>
|
||||
<div className="value">{this.props.instanceNumber}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="item item-frames clearfix">
|
||||
<div className="icon">
|
||||
<div />
|
||||
</div>
|
||||
<div className="value">{this.props.numImageFrames}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
};
|
||||
|
||||
onDoubleClick = () => {
|
||||
if (this.props.onDoubleClick) {
|
||||
this.props.onDoubleClick();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { ThumbnailEntry };
|
||||
@@ -0,0 +1,127 @@
|
||||
@import './../../design/styles/common/global.styl'
|
||||
|
||||
:root {
|
||||
--series-count-background-color: #678696;
|
||||
}
|
||||
|
||||
.ThumbnailEntry
|
||||
cursor: pointer
|
||||
display: table
|
||||
|
||||
.p-x-1
|
||||
padding: 0 1rem
|
||||
|
||||
.alt-image-text
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--primary-background-color);
|
||||
box-shadow: inset 0 0 0 1px var(--ui-border-color-dark);
|
||||
border: 5px solid transparent;
|
||||
border-radius: 12px;
|
||||
height: 135px;
|
||||
margin: 0 auto;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
transition: var(--sidebar-transition);
|
||||
width: 217px;
|
||||
display: flex;
|
||||
|
||||
h1
|
||||
text-align: center
|
||||
color: var(--text-primary-color);
|
||||
|
||||
.series-details
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
color: var(--text-primary-color);
|
||||
font-size: 14px
|
||||
line-height: 1.3em
|
||||
margin-top: 5px
|
||||
margin-left: 1rem
|
||||
margin-right: 0.5rem
|
||||
max-width: 217px
|
||||
min-height: 36px
|
||||
position: relative
|
||||
word-wrap: break-word
|
||||
|
||||
.series-description
|
||||
flex-grow: 1
|
||||
|
||||
&.info-only
|
||||
.series-description
|
||||
display: none
|
||||
|
||||
.series-information
|
||||
display: flex
|
||||
flex-grow: 1
|
||||
float: none
|
||||
max-width: none
|
||||
padding-right: 0
|
||||
|
||||
.item
|
||||
flex: 1
|
||||
text-align: center
|
||||
|
||||
.icon, .value
|
||||
display: inline
|
||||
float: none
|
||||
line-height: 25px
|
||||
|
||||
.value
|
||||
margin-left: 0
|
||||
width: auto
|
||||
|
||||
.series-information
|
||||
padding-right: 4px
|
||||
max-width: 50px
|
||||
|
||||
.item-frames .icon
|
||||
height: 18px
|
||||
|
||||
.value
|
||||
color: var(--text-secondary-color);
|
||||
display: inline-block
|
||||
float: right
|
||||
font-size: 12px
|
||||
margin-left: 4px
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
width: calc(100% - 15px)
|
||||
|
||||
.icon
|
||||
color: var(--active-color);
|
||||
display: inline-block
|
||||
float: left
|
||||
font-size: 10px
|
||||
font-weight: 900
|
||||
text-align: right
|
||||
width: 11px
|
||||
|
||||
div
|
||||
background-color: var(--series-count-background-color);
|
||||
margin-top: 6px
|
||||
position: relative
|
||||
|
||||
&:after
|
||||
background-color: var(--active-color);
|
||||
box-shadow: 1px 1px rgba(0, 0, 0, .115)
|
||||
left: -4px
|
||||
position: absolute
|
||||
top: -4px
|
||||
|
||||
&
|
||||
&:after
|
||||
border: 0px solid var(--series-count-background-color)
|
||||
content: ''
|
||||
display: inline-block
|
||||
height: 11px
|
||||
width: 11px
|
||||
|
||||
.image-thumbnail-clone
|
||||
z-index: 100000
|
||||
pointer-events: none
|
||||
|
||||
&:hover
|
||||
border-color: var(--hover-color);
|
||||
box-shadow: none
|
||||
@@ -0,0 +1,64 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DragSource } from 'react-dnd';
|
||||
import DragPreview from './DragPreview';
|
||||
import { ThumbnailEntry } from './ThumbnailEntry.js';
|
||||
|
||||
// Drag sources and drop targets only interact
|
||||
// if they have the same string type.
|
||||
const Types = {
|
||||
THUMBNAIL: 'thumbnail',
|
||||
};
|
||||
|
||||
const thumbnailSource = {
|
||||
/*canDrag(props) {
|
||||
return props.error === false;
|
||||
},*/
|
||||
|
||||
beginDrag(props) {
|
||||
return props;
|
||||
},
|
||||
|
||||
endDrag(props, monitor) {
|
||||
//const item = monitor.getItem();
|
||||
const dropResult = monitor.getDropResult();
|
||||
|
||||
if (dropResult) {
|
||||
//console.log(`You dropped ${item.id} into ${dropResult.id}!`);
|
||||
//console.log(item);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
class ThumbnailEntryDragSource extends Component {
|
||||
static propTypes = {
|
||||
connectDragSource: PropTypes.func.isRequired,
|
||||
isDragging: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
isDragging: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { connectDragSource } = this.props;
|
||||
const dropEffect = 'copy';
|
||||
|
||||
return connectDragSource(
|
||||
<div className="ThumbnailEntryContainer">
|
||||
<DragPreview {...this.props} />
|
||||
<ThumbnailEntry {...this.props} />
|
||||
</div>,
|
||||
{ dropEffect }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const collect = (connect, monitor) => ({
|
||||
connectDragSource: connect.dragSource(),
|
||||
isDragging: monitor.isDragging(),
|
||||
});
|
||||
|
||||
export default DragSource(Types.THUMBNAIL, thumbnailSource, collect)(
|
||||
ThumbnailEntryDragSource
|
||||
);
|
||||
@@ -0,0 +1,75 @@
|
||||
export const studies = [
|
||||
{
|
||||
thumbnails: [
|
||||
{
|
||||
imageSrc:
|
||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_Lung.jpg',
|
||||
seriesDescription: 'Anti-PD-1_Lung',
|
||||
active: true,
|
||||
seriesNumber: '2',
|
||||
numImageFrames: 512,
|
||||
stackPercentComplete: 30,
|
||||
},
|
||||
{
|
||||
imageSrc:
|
||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_MELANOMA.jpg',
|
||||
seriesDescription: 'Anti-PD-1_MELANOMA',
|
||||
seriesNumber: '2',
|
||||
instanceNumber: '1',
|
||||
numImageFrames: 256,
|
||||
stackPercentComplete: 70,
|
||||
},
|
||||
{
|
||||
altImageText: 'SR',
|
||||
seriesDescription: 'Imaging Measurement Report',
|
||||
seriesNumber: '3',
|
||||
stackPercentComplete: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
thumbnails: [
|
||||
{
|
||||
imageSrc:
|
||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-GBM.jpg',
|
||||
seriesDescription: 'CPTAC-GBM',
|
||||
active: true,
|
||||
seriesNumber: '2',
|
||||
numImageFrames: 512,
|
||||
stackPercentComplete: 100,
|
||||
},
|
||||
{
|
||||
imageSrc:
|
||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-CM.jpg',
|
||||
seriesDescription: 'CPTAC-CM',
|
||||
seriesNumber: '2',
|
||||
instanceNumber: '1',
|
||||
numImageFrames: 256,
|
||||
},
|
||||
{
|
||||
imageSrc:
|
||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-HNSCC.jpg',
|
||||
seriesDescription: 'CPTAC-HNSCC',
|
||||
seriesNumber: '2',
|
||||
instanceNumber: '1',
|
||||
numImageFrames: 256,
|
||||
},
|
||||
{
|
||||
imageSrc:
|
||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-LSCC.jpg',
|
||||
seriesDescription: 'CPTAC-LSCC',
|
||||
seriesNumber: '2',
|
||||
instanceNumber: '1',
|
||||
numImageFrames: 256,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export function onThumbnailClick() {
|
||||
console.warn('onThumbnailClick');
|
||||
}
|
||||
|
||||
export function onThumbnailDoubleClick() {
|
||||
console.warn('onThumbnailDoubleClick');
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: Study Browser
|
||||
menu: Components
|
||||
route: /components/study-browser
|
||||
---
|
||||
|
||||
import { Playground, Props } from 'docz'
|
||||
import { State } from 'react-powerplug'
|
||||
import { WrappedStudyBrowser } from './wrappedStudyBrowser.js'
|
||||
import { StudyBrowser } from './../index.js'
|
||||
|
||||
# Study Browser
|
||||
|
||||
## Basic usage
|
||||
|
||||
<Playground>
|
||||
<WrappedStudyBrowser />
|
||||
</Playground>
|
||||
|
||||
## API
|
||||
|
||||
<Props of={StudyBrowser} />
|
||||
@@ -0,0 +1,40 @@
|
||||
import React, { Component } from 'react';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
import TouchBackend from 'react-dnd-touch-backend';
|
||||
|
||||
//
|
||||
import {
|
||||
studies,
|
||||
onThumbnailClick,
|
||||
onThumbnailDoubleClick,
|
||||
} from './exampleStudies.js';
|
||||
import { ExampleDropTarget, StudyBrowser } from './../index.js';
|
||||
|
||||
class StudyBrowserContainer extends Component {
|
||||
render() {
|
||||
//const viewportData = [null, null, null, null];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ExampleDropTarget />
|
||||
<StudyBrowser
|
||||
studies={studies}
|
||||
onThumbnailClick={onThumbnailClick}
|
||||
onThumbnailDoubleClick={onThumbnailDoubleClick}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Note:
|
||||
// Normally, the top level APP component is wrapped with the DragDropContext
|
||||
// We wrap this component to create a simple/local example.
|
||||
const WrappedStudyBrowser = DragDropContext(
|
||||
TouchBackend({ enableMouseEvents: true }),
|
||||
null,
|
||||
true
|
||||
)(StudyBrowserContainer);
|
||||
|
||||
// http://react-dnd.github.io/react-dnd/docs/api/drag-drop-context
|
||||
export { WrappedStudyBrowser };
|
||||
@@ -0,0 +1,3 @@
|
||||
export { ExampleDropTarget } from './ExampleDropTarget.js';
|
||||
export { StudyBrowser } from './StudyBrowser.js';
|
||||
export { ThumbnailEntry } from './ThumbnailEntry.js';
|
||||
Reference in new issue
Block a user