Work on refactoring measurements out of Lesion Tracker package
- Removed AssociatedStudies Collection - Measurement API is generated from configuration files - Data exchange methods are defined in configuration
This commit is contained in:
1 parent
c511edd419
commit
ab4d4c9008
537 files changed
+5122
-3545
No files matched your search
+30
@@ -0,0 +1,30 @@
|
||||
<template name="thumbnailEntry">
|
||||
{{#let stack=this.thumbnail.stack}}
|
||||
<div class="thumbnailEntry noselect m-t-1 m-b-2 {{draggableClass}} {{#if isDisplaySetActive stack.displaySetInstanceUid viewportIndex}}active{{/if}}">
|
||||
<div class="p-x-1">
|
||||
{{>imageThumbnail (clone this)}}
|
||||
</div>
|
||||
<div class="seriesDetails clearfix m-x-1 {{#unless stack.seriesDescription}}info-only{{/unless}}">
|
||||
<div class="seriesDescription">
|
||||
{{stack.seriesDescription}}
|
||||
</div>
|
||||
<div class="seriesInformation">
|
||||
<div class="item item-series clearfix">
|
||||
<div class="icon">S:</div>
|
||||
<div class="value">{{stack.seriesNumber}}</div>
|
||||
</div>
|
||||
{{#unless isUndefined instanceNumber}}
|
||||
<div class="item item-series clearfix">
|
||||
<div class="icon">I:</div>
|
||||
<div class="value">{{instanceNumber}}</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
<div class="item item-frames clearfix">
|
||||
<div class="icon"><div></div></div>
|
||||
<div class="value">{{stack.numImageFrames}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/let}}
|
||||
</template>
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
const cloneElement = (element, targetId) => {
|
||||
// Clone the DOM element
|
||||
const clone = element.cloneNode(true);
|
||||
|
||||
// Find any canvas children to clone
|
||||
const clonedCanvases = $(clone).find('canvas');
|
||||
clonedCanvases.each((canvasIndex, clonedCanvas) => {
|
||||
// Draw from the original canvas to the cloned canvas
|
||||
const context = clonedCanvas.getContext('2d');
|
||||
const thumbnailCanvas = $(element).find('canvas').get(canvasIndex);
|
||||
context.drawImage(thumbnailCanvas, 0, 0);
|
||||
});
|
||||
|
||||
// Update the clone with the targetId
|
||||
clone.id = targetId;
|
||||
clone.style.visibility = 'hidden';
|
||||
return clone;
|
||||
};
|
||||
|
||||
const thumbnailDragStartHandler = (event, data) => {
|
||||
// Prevent any scrolling behaviour normally caused by the original event
|
||||
event.originalEvent.preventDefault();
|
||||
|
||||
// Identify the current study and series index from the thumbnail's DOM position
|
||||
const targetThumbnail = event.currentTarget;
|
||||
const $imageThumbnail = $(targetThumbnail);
|
||||
|
||||
// Clone the image thumbnail
|
||||
const targetId = 'DragClone';
|
||||
const clone = cloneElement(targetThumbnail, targetId);
|
||||
const $clone = $(clone);
|
||||
$clone.addClass('imageThumbnailClone');
|
||||
|
||||
// Set pointerEvents to pass through the clone DOM element
|
||||
// This is necessary in order to identify what is below it
|
||||
// when using document.elementFromPoint
|
||||
clone.style.pointerEvents = 'none';
|
||||
|
||||
// Append the clone to the body
|
||||
document.body.appendChild(clone);
|
||||
|
||||
// Set the cursor x and y positions from the current touch/mouse coordinates
|
||||
let cursorX, cursorY;
|
||||
// Handle touchStart cases
|
||||
if (event.type === 'touchstart') {
|
||||
cursorX = event.originalEvent.touches[0].pageX;
|
||||
cursorY = event.originalEvent.touches[0].pageY;
|
||||
} else {
|
||||
cursorX = event.pageX;
|
||||
cursorY = event.pageY;
|
||||
|
||||
// Also hook up event handlers for mouse events
|
||||
const handlers = {};
|
||||
handlers.mousemove = event => thumbnailDragHandler(event);
|
||||
handlers.mouseup = event => thumbnailDragEndHandler(event, data, handlers);
|
||||
$(document).on('mousemove', handlers.mousemove);
|
||||
$(document).on('mouseup', handlers.mouseup);
|
||||
}
|
||||
|
||||
// This block gets the current offset of the touch/mouse
|
||||
// relative to the window
|
||||
//
|
||||
// i.e. Where did the user grab it from?
|
||||
const offset = $imageThumbnail.offset();
|
||||
const left = offset.left;
|
||||
const top = offset.top;
|
||||
|
||||
// This difference is saved for later so the element movement looks normal
|
||||
const diff = {
|
||||
x: cursorX - left,
|
||||
y: cursorY - top
|
||||
};
|
||||
$clone.data('diff', diff);
|
||||
|
||||
// This sets the default style properties of the cloned element so it is
|
||||
// ready to be dragged around the page
|
||||
$clone.css({
|
||||
left: cursorX - diff.x,
|
||||
position: 'fixed',
|
||||
top: cursorY - diff.y,
|
||||
visibility: 'hidden',
|
||||
'z-index': 100000
|
||||
});
|
||||
};
|
||||
|
||||
const thumbnailDragHandler = event => {
|
||||
// Get the touch/mouse coordinates from the event
|
||||
let cursorX, cursorY;
|
||||
if (event.type === 'touchmove') {
|
||||
cursorX = event.originalEvent.changedTouches[0].pageX;
|
||||
cursorY = event.originalEvent.changedTouches[0].pageY;
|
||||
} else {
|
||||
cursorX = event.pageX;
|
||||
cursorY = event.pageY;
|
||||
}
|
||||
|
||||
// Find the clone element and update it's position on the page
|
||||
const $clone = $('#DragClone');
|
||||
const diff = $clone.data('diff');
|
||||
$clone.css({
|
||||
left: cursorX - diff.x,
|
||||
position: 'fixed',
|
||||
top: cursorY - diff.y,
|
||||
visibility: 'visible',
|
||||
'z-index': 100000
|
||||
});
|
||||
|
||||
// Identify the element below the current cursor position
|
||||
const elemBelow = document.elementFromPoint(cursorX, cursorY);
|
||||
|
||||
// If none exists, stop here
|
||||
if (!elemBelow) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any current faded effects on viewports
|
||||
$('.imageViewerViewport canvas').removeClass('faded');
|
||||
|
||||
// Figure out what to do depending on what we're dragging over
|
||||
const $viewportsDraggedOver = $(elemBelow).parents('.imageViewerViewport');
|
||||
if ($viewportsDraggedOver.length) {
|
||||
// If we're dragging over a non-empty viewport, fade it and change the cursor style
|
||||
$viewportsDraggedOver.find('canvas').not('.magnifyTool').addClass('faded');
|
||||
document.body.style.cursor = 'copy';
|
||||
} else if (elemBelow.classList.contains('imageViewerViewport') && elemBelow.classList.contains('empty')) {
|
||||
// If we're dragging over an empty viewport, just change the cursor style
|
||||
document.body.style.cursor = 'copy';
|
||||
} else {
|
||||
// Otherwise, keep the cursor as no-drop style
|
||||
document.body.style.cursor = 'no-drop';
|
||||
}
|
||||
};
|
||||
|
||||
const thumbnailDragEndHandler = (event, data, handlers) => {
|
||||
// Remove the mouse event listeners
|
||||
if (handlers) {
|
||||
$(document).off('mousemove', handlers.mousemove);
|
||||
$(document).off('mouseup', handlers.mouseup);
|
||||
}
|
||||
|
||||
// Reset the cursor style to the default
|
||||
document.body.style.cursor = 'auto';
|
||||
|
||||
// Get the cloned element
|
||||
const $clone = $('#DragClone');
|
||||
|
||||
// If it doesn't exist, stop here
|
||||
if (!$clone.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const top = $clone.offset().top;
|
||||
const left = $clone.offset().left;
|
||||
const diff = $clone.data('diff');
|
||||
|
||||
// Identify the element below the cloned element position
|
||||
const elemBelow = document.elementFromPoint(left + diff.x, top + diff.y);
|
||||
|
||||
// Remove all cloned elements from the page
|
||||
$('.imageThumbnailClone').remove();
|
||||
|
||||
// Remove any current faded effects on viewports
|
||||
$('.imageViewerViewport canvas').removeClass('faded');
|
||||
|
||||
// If none exists, stop here
|
||||
if (!elemBelow) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any fade effects on the element below
|
||||
elemBelow.classList.remove('faded');
|
||||
|
||||
let element;
|
||||
const $viewportsDraggedOver = $(elemBelow).closest('.imageViewerViewport');
|
||||
if ($viewportsDraggedOver.length) {
|
||||
// If we're dragging over a non-empty viewport, retrieve it
|
||||
element = $viewportsDraggedOver.get(0);
|
||||
} else if (elemBelow.classList.contains('imageViewerViewport') &&
|
||||
elemBelow.classList.contains('empty')) {
|
||||
// If we're dragging over an empty viewport, retrieve that instead
|
||||
element = elemBelow;
|
||||
} else {
|
||||
// Otherwise, stop here
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no stored drag and drop data, stop here
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the dropped viewport index
|
||||
const viewportIndex = $('.imageViewerViewport').index(element);
|
||||
|
||||
// Rerender the viewport using the dragged thumbnail data
|
||||
window.layoutManager.rerenderViewportWithNewDisplaySet(viewportIndex, data);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
Template.thumbnailEntry.onCreated(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
// Check if the thumbnails will be draggable or clickable
|
||||
instance.isDragAndDrop = _.isUndefined(instance.data.viewportIndex);
|
||||
});
|
||||
|
||||
Template.thumbnailEntry.onRendered(() => {
|
||||
console.warn('thumbnailEntry onRendered');
|
||||
});
|
||||
|
||||
Template.thumbnailEntry.events({
|
||||
// Event handlers for drag and drop
|
||||
'touchstart .thumbnailEntry, mousedown .thumbnailEntry'(event, instance) {
|
||||
const data = instance.data.thumbnail.stack;
|
||||
instance.isDragAndDrop && thumbnailDragStartHandler(event, data);
|
||||
},
|
||||
|
||||
'touchmove .thumbnailEntry'(event, instance) {
|
||||
instance.isDragAndDrop && thumbnailDragHandler(event);
|
||||
},
|
||||
|
||||
'touchend .thumbnailEntry'(event, instance) {
|
||||
const data = instance.data.thumbnail.stack;
|
||||
instance.isDragAndDrop && thumbnailDragEndHandler(event, data);
|
||||
},
|
||||
|
||||
// Event handlers for click (quick switch)
|
||||
'click .thumbnailEntry'(event, instance) {
|
||||
if (!instance.isDragAndDrop) {
|
||||
// Get the thumbnail stack data
|
||||
const data = instance.data.thumbnail.stack;
|
||||
|
||||
// Rerender the viewport using the clicked thumbnail data
|
||||
window.layoutManager.rerenderViewportWithNewDisplaySet(instance.data.viewportIndex, data);
|
||||
}
|
||||
},
|
||||
|
||||
// Event handlers for double click
|
||||
'dblclick .thumbnailEntry'(event, instance) {
|
||||
// Get the active viewport index
|
||||
const viewportIndex = Session.get('activeViewport') || 0;
|
||||
|
||||
// Get the thumbnail stack data
|
||||
const data = instance.data.thumbnail.stack;
|
||||
|
||||
// Rerender the viewport using the clicked thumbnail data
|
||||
window.layoutManager.rerenderViewportWithNewDisplaySet(viewportIndex, data);
|
||||
}
|
||||
});
|
||||
|
||||
Template.thumbnailEntry.helpers({
|
||||
draggableClass() {
|
||||
return Template.instance().isDragAndDrop ? 'draggable' : '';
|
||||
},
|
||||
instanceNumber() {
|
||||
const thumbnail = Template.instance().data.thumbnail;
|
||||
if (!thumbnail) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stack = thumbnail.stack;
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to show instance number for single-frame images
|
||||
if (!stack.isMultiFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstImage = stack.images[0];
|
||||
if (!firstImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
return firstImage.instanceNumber;
|
||||
}
|
||||
});
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
@import "{design}/app"
|
||||
|
||||
$seriesCountBackgroundColor = #678696
|
||||
|
||||
.thumbnailEntry
|
||||
cursor: pointer
|
||||
display: table
|
||||
margin: 0 auto
|
||||
|
||||
&.draggable
|
||||
cursor: copy
|
||||
cursor: -webkit-grab
|
||||
cursor: -moz-grab
|
||||
|
||||
.seriesDetails
|
||||
theme('color', '$textPrimaryColor')
|
||||
font-size: 14px
|
||||
line-height: 1.3em
|
||||
margin-top: 5px
|
||||
max-width: 217px
|
||||
min-height: 36px
|
||||
position: relative
|
||||
word-wrap: break-word
|
||||
|
||||
&.info-only
|
||||
min-height: auto
|
||||
|
||||
.seriesDescription
|
||||
display: none
|
||||
|
||||
.seriesInformation
|
||||
display: flex
|
||||
float: none
|
||||
max-width: none
|
||||
padding-right: none
|
||||
|
||||
.item
|
||||
flex: 1
|
||||
text-align: center
|
||||
|
||||
.icon, .value
|
||||
display: inline
|
||||
float: none
|
||||
line-height: 25px
|
||||
|
||||
.value
|
||||
margin-left: 0
|
||||
width: auto
|
||||
|
||||
.seriesDescription
|
||||
float: left
|
||||
width: calc(100% - 50px)
|
||||
|
||||
.seriesInformation
|
||||
display: table
|
||||
float: right
|
||||
padding-right: 4px
|
||||
max-width: 50px
|
||||
|
||||
.item-frames .icon
|
||||
height: 18px
|
||||
|
||||
.value
|
||||
theme('color', '$textSecondaryColor')
|
||||
display: inline-block
|
||||
float: right
|
||||
font-size: 12px
|
||||
margin-left: 4px
|
||||
width: calc(100% - 15px)
|
||||
|
||||
.icon
|
||||
theme('color', '$activeColor')
|
||||
display: inline-block
|
||||
float: left
|
||||
font-size: 10px
|
||||
font-weight: 900
|
||||
text-align: right
|
||||
width: 11px
|
||||
|
||||
div
|
||||
background-color: $seriesCountBackgroundColor
|
||||
margin-top: 6px
|
||||
position: relative
|
||||
|
||||
&:after
|
||||
theme('background-color', '$activeColor')
|
||||
box-shadow: 1px 1px rgba(0, 0, 0, .115)
|
||||
left: -5px
|
||||
position: absolute
|
||||
top: -5px
|
||||
|
||||
&
|
||||
&:after
|
||||
theme('border', '1px solid $primaryBackgroundColor')
|
||||
content: ''
|
||||
display: inline-block
|
||||
height: 11px
|
||||
width: 11px
|
||||
Reference in new issue
Block a user