LT-251: Disabling drag and drop from series and adding click-to-select behavior for quick switch areas

This commit is contained in:
Bruno Alves de Faria 2016-06-15 11:14:30 -03:00 committed by Erik Ziegler
parent 97c8c2b554
commit e3fc56c0f4
13 changed files with 200 additions and 154 deletions

View File

@ -5,7 +5,7 @@
<div class="studySwitch"></div> <div class="studySwitch"></div>
<div class="switchHover studyHover clearfix"> <div class="switchHover studyHover clearfix">
<div class="scrollArea"> <div class="scrollArea">
{{>studyTimepointBrowser timepointViewType=timepointViewType}} {{>studyTimepointBrowser timepointViewType=timepointViewType viewportIndex=viewportIndex}}
</div> </div>
</div> </div>
</div> </div>
@ -26,7 +26,7 @@
<div class="switchHover seriesHover clearfix"> <div class="switchHover seriesHover clearfix">
<div class="scrollArea"> <div class="scrollArea">
{{#each thumbnail in (thumbnails currentStudy)}} {{#each thumbnail in (thumbnails currentStudy)}}
{{>thumbnailEntry thumbnail}} {{>thumbnailEntry thumbnail=thumbnail viewportIndex=viewportIndex}}
{{/each}} {{/each}}
</div> </div>
</div> </div>

View File

@ -13,7 +13,6 @@ Template.studySeriesQuickSwitch.helpers({
}, },
currentStudy() { currentStudy() {
console.log('currentStudy');
var viewportIndex = Template.instance().data.viewportIndex; var viewportIndex = Template.instance().data.viewportIndex;
Session.get('CornerstoneNewImage' + viewportIndex); Session.get('CornerstoneNewImage' + viewportIndex);
@ -31,7 +30,6 @@ Template.studySeriesQuickSwitch.helpers({
}, },
currentTimepoint() { currentTimepoint() {
console.log('currentStudy');
var viewportIndex = Template.instance().data.viewportIndex; var viewportIndex = Template.instance().data.viewportIndex;
Session.get('CornerstoneNewImage' + viewportIndex); Session.get('CornerstoneNewImage' + viewportIndex);

View File

@ -30,6 +30,8 @@ $switchWrapperWidth = 140px
opacity: 0 opacity: 0
.switchHover .switchHover
opacity: 0.8 opacity: 0.8
.studyHover
transform(scale(1))
.label .label
color: $textSecondaryColor color: $textSecondaryColor
@ -114,22 +116,22 @@ $switchWrapperWidth = 140px
$switchSizeSmall = 35px $switchSizeSmall = 35px
@media screen and (max-width: 1024px) @media screen and (max-width: 1024px)
.switchSection:hover .switchHover .switchSection:hover .seriesHover
transform(scale(0.5)) transform(scale(0.5))
@media screen and (min-width: 1025px) and (max-width: 1152px) @media screen and (min-width: 1025px) and (max-width: 1152px)
.switchSection:hover .switchHover .switchSection:hover .seriesHover
transform(scale(0.6)) transform(scale(0.6))
@media screen and (min-width: 1153px) and (max-width: 1280px) @media screen and (min-width: 1153px) and (max-width: 1280px)
.switchSection:hover .switchHover .switchSection:hover .seriesHover
transform(scale(0.7)) transform(scale(0.7))
@media screen and (min-width: 1281px) and (max-width: 1440px) @media screen and (min-width: 1281px) and (max-width: 1440px)
.switchSection:hover .switchHover .switchSection:hover .seriesHover
transform(scale(0.8)) transform(scale(0.8))
@media screen and (min-width: 1441px) and (max-width: 1600px) @media screen and (min-width: 1441px) and (max-width: 1600px)
.switchSection:hover .switchHover .switchSection:hover .seriesHover
transform(scale(0.9)) transform(scale(0.9))
@media screen and (min-width: 1601px) @media screen and (min-width: 1601px)
.switchSection:hover .switchHover .switchSection:hover .seriesHover
transform(scale(1)) transform(scale(1))
@media screen and (max-width: 1600px) @media screen and (max-width: 1600px)

View File

@ -1,8 +1,8 @@
<template name="studyTimepoint"> <template name="studyTimepoint">
<div class="studyTimepointWrapper"> <div class="studyTimepointWrapper">
<div class="studyTimepoint"> <div class="studyTimepoint">
{{#each studies}} {{#each study in studies}}
{{>studyTimepointStudy study=this active=isActive}} {{>studyTimepointStudy study=study active=(isActive study) viewportIndex=viewportIndex}}
{{/each}} {{/each}}
</div> </div>
</div> </div>

View File

@ -54,11 +54,11 @@ Template.studyTimepoint.events({
}); });
Template.studyTimepoint.helpers({ Template.studyTimepoint.helpers({
isActive() { isActive(study) {
if (!this.studyInstanceUid) { if (!study.studyInstanceUid) {
return; return;
} }
return isActive[this.studyInstanceUid]; return isActive[study.studyInstanceUid];
} }
}) })

View File

@ -17,7 +17,7 @@
</div> </div>
<div class="timepointModalities">{{modalitiesSummary timepoint}}</div> <div class="timepointModalities">{{modalitiesSummary timepoint}}</div>
</div> </div>
{{>studyTimepoint studies=(studies timepoint) index=@index}} {{>studyTimepoint studies=(studies timepoint) index=@index viewportIndex=viewportIndex}}
</div> </div>
<hr class="m-y-1"> <hr class="m-y-1">
{{/if}} {{/if}}

View File

@ -16,7 +16,7 @@
</div> </div>
<div class="studyTimepointThumbnails"> <div class="studyTimepointThumbnails">
{{#each thumbnail in (thumbnails this.study)}} {{#each thumbnail in (thumbnails this.study)}}
{{>thumbnailEntry thumbnail}} {{>thumbnailEntry thumbnail=thumbnail viewportIndex=viewportIndex}}
{{/each}} {{/each}}
</div> </div>
</div> </div>

View File

@ -1,11 +1,11 @@
<template name="studyBrowser"> <template name="studyBrowser">
<div class="studyBrowser"> <div class="studyBrowser">
<div class="scrollableStudyThumbnails"> <div class="scrollableStudyThumbnails">
{{ #each studies }} {{#each study in studies}}
{{#each thumbnails}} {{#each thumbnail in thumbnails}}
{{ >thumbnailEntry }} {{>thumbnailEntry thumbnail=thumbnail}}
{{/each}}
{{/each}} {{/each}}
{{ /each }}
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,11 +1,11 @@
<template name="thumbnailEntry"> <template name="thumbnailEntry">
<div class="thumbnailEntry m-t-1 m-b-2" data-uid="{{this.stack.seriesInstanceUid}}"> <div class="thumbnailEntry m-t-1 m-b-2 {{draggableClass}} {{#if isSeriesActive this.thumbnail.stack.seriesInstanceUid viewportIndex}}active{{/if}}">
<div class="p-x-1"> <div class="p-x-1">
{{>imageThumbnail}} {{>imageThumbnail this.thumbnail}}
</div> </div>
<div class="seriesDetails noselect m-a-1"> <div class="seriesDetails noselect m-a-1">
<div class="seriesDescription"> <div class="seriesDescription">
{{this.stack.seriesDescription}} {{this.thumbnail.stack.seriesDescription}}
</div> </div>
<div class="seriesInformation"> <div class="seriesInformation">
<div class="seriesInformationIcons"> <div class="seriesInformationIcons">
@ -13,8 +13,8 @@
<div></div> <div></div>
</div> </div>
<div class="seriesInformationNumbers"> <div class="seriesInformationNumbers">
<div>{{this.stack.seriesNumber}}</div> <div>{{this.thumbnail.stack.seriesNumber}}</div>
<div>{{this.stack.instances.length}}</div> <div>{{this.thumbnail.stack.instances.length}}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,13 +1,13 @@
function cloneElement(element, targetId) { const cloneElement = (element, targetId) => {
// Clone the DOM element // Clone the DOM element
var clone = element.cloneNode(true); const clone = element.cloneNode(true);
// Find any canvas children to clone // Find any canvas children to clone
var clonedCanvases = $(clone).find('canvas'); const clonedCanvases = $(clone).find('canvas');
clonedCanvases.each(function(canvasIndex, clonedCanvas) { clonedCanvases.each((canvasIndex, clonedCanvas) => {
// Draw from the original canvas to the cloned canvas // Draw from the original canvas to the cloned canvas
var context = clonedCanvas.getContext('2d'); const context = clonedCanvas.getContext('2d');
var thumbnailCanvas = $(element).find('canvas').get(canvasIndex); const thumbnailCanvas = $(element).find('canvas').get(canvasIndex);
context.drawImage(thumbnailCanvas, 0, 0); context.drawImage(thumbnailCanvas, 0, 0);
}); });
@ -15,97 +15,98 @@ function cloneElement(element, targetId) {
clone.id = targetId; clone.id = targetId;
clone.style.visibility = 'hidden'; clone.style.visibility = 'hidden';
return clone; return clone;
} };
function thumbnailDragStartHandler(e, data) { const thumbnailDragStartHandler = (event, data) => {
// Prevent any scrolling behaviour normally caused by the original event // Prevent any scrolling behaviour normally caused by the original event
e.originalEvent.preventDefault(); event.originalEvent.preventDefault();
// Identify the current study and series index from the thumbnail's DOM position // Identify the current study and series index from the thumbnail's DOM position
var targetThumbnail = e.currentTarget; const targetThumbnail = event.currentTarget;
var imageThumbnail = $(targetThumbnail); const $imageThumbnail = $(targetThumbnail);
// Store this data for use during drag and drop
OHIF.viewer.dragAndDropData = data;
// Clone the image thumbnail // Clone the image thumbnail
var targetId = targetThumbnail.id + 'DragClone'; const targetId = 'DragClone';
var clone = cloneElement(targetThumbnail, targetId); const clone = cloneElement(targetThumbnail, targetId);
clone.classList.add('imageThumbnailClone'); const $clone = $(clone);
$clone.addClass('imageThumbnailClone');
// Set pointerEvents to pass through the clone DOM element // Set pointerEvents to pass through the clone DOM element
// This is necessary in order to identify what is below it // This is necessary in order to identify what is below it
// when using document.elementFromPoint // when using document.elementFromPoint
clone.style.pointerEvents = 'none'; clone.style.pointerEvents = 'none';
// Append the clone to the parent of the target // Append the clone to the body
targetThumbnail.parentNode.appendChild(clone); document.body.appendChild(clone);
// Set the cursor x and y positions from the current touch/mouse coordinates // Set the cursor x and y positions from the current touch/mouse coordinates
let cursorX, cursorY;
// Handle touchStart cases // Handle touchStart cases
if (e.type === 'touchstart') { if (event.type === 'touchstart') {
cursorX = e.originalEvent.touches[0].pageX; cursorX = event.originalEvent.touches[0].pageX;
cursorY = e.originalEvent.touches[0].pageY; cursorY = event.originalEvent.touches[0].pageY;
} else { } else {
cursorX = e.pageX; cursorX = event.pageX;
cursorY = e.pageY; cursorY = event.pageY;
// Also hook up event handlers for mouse events // Also hook up event handlers for mouse events
$(document).on('mousemove', function(e) { const handlers = {};
thumbnailDragHandler(e, targetThumbnail); handlers.mousemove = event => thumbnailDragHandler(event);
}); handlers.mouseup = event => thumbnailDragEndHandler(event, data, handlers);
$(document).on('mouseup', function(e) { $(document).on('mousemove', handlers.mousemove);
thumbnailDragEndHandler(e, targetThumbnail); $(document).on('mouseup', handlers.mouseup);
});
} }
// This block gets the current position of the touch/mouse // This block gets the current offset of the touch/mouse
// relative to the thumbnail itself // relative to the window
// //
// i.e. Where did the user grab it from? // i.e. Where did the user grab it from?
var position = imageThumbnail.position(); const offset = $imageThumbnail.offset();
var left = position.left; const left = offset.left;
var top = position.top; const top = offset.top;
// This difference is saved for later so the element movement looks normal // This difference is saved for later so the element movement looks normal
diffX = cursorX - left; const diff = {
diffY = cursorY - top; x: cursorX - left,
y: cursorY - top
};
$clone.data('diff', diff);
// This sets the default style properties of the cloned element so it is // This sets the default style properties of the cloned element so it is
// ready to be dragged around the page // ready to be dragged around the page
$(clone).css({ $clone.css({
top: top, left: cursorX - diff.x,
left: left, position: 'fixed',
position: 'absolute', top: cursorY - diff.y,
'z-index': 100, visibility: 'hidden',
visibility: 'hidden' 'z-index': 100000
}); });
} };
function thumbnailDragHandler(e, target) { const thumbnailDragHandler = event => {
// Get the touch/mouse coordinates from the event // Get the touch/mouse coordinates from the event
var cursorX, let cursorX, cursorY;
cursorY; if (event.type === 'touchmove') {
if (e.type === 'touchmove') { cursorX = event.originalEvent.changedTouches[0].pageX;
cursorX = e.originalEvent.changedTouches[0].pageX; cursorY = event.originalEvent.changedTouches[0].pageY;
cursorY = e.originalEvent.changedTouches[0].pageY;
} else { } else {
cursorX = e.pageX; cursorX = event.pageX;
cursorY = e.pageY; cursorY = event.pageY;
} }
// Find the clone element and update it's position on the page // Find the clone element and update it's position on the page
var clone = $('#' + target.id + 'DragClone'); const $clone = $('#DragClone');
clone.css({ const diff = $clone.data('diff');
top: cursorY - diffY, $clone.css({
left: cursorX - diffX, left: cursorX - diff.x,
position: 'absolute', position: 'fixed',
'z-index': 100, top: cursorY - diff.y,
visibility: 'visible' visibility: 'visible',
'z-index': 100000
}); });
// Identify the element below the current cursor position // Identify the element below the current cursor position
var elemBelow = document.elementFromPoint(cursorX + diffX, cursorY + diffY); const elemBelow = document.elementFromPoint(cursorX, cursorY);
// If none exists, stop here // If none exists, stop here
if (!elemBelow) { if (!elemBelow) {
@ -116,10 +117,10 @@ function thumbnailDragHandler(e, target) {
$('.imageViewerViewport canvas').removeClass('faded'); $('.imageViewerViewport canvas').removeClass('faded');
// Figure out what to do depending on what we're dragging over // Figure out what to do depending on what we're dragging over
var viewportsDraggedOver = $(elemBelow).parents('.imageViewerViewport'); const $viewportsDraggedOver = $(elemBelow).parents('.imageViewerViewport');
if (viewportsDraggedOver.length) { if ($viewportsDraggedOver.length) {
// If we're dragging over a non-empty viewport, fade it and change the cursor style // If we're dragging over a non-empty viewport, fade it and change the cursor style
viewportsDraggedOver.find('canvas').not('.magnifyTool').addClass('faded'); $viewportsDraggedOver.find('canvas').not('.magnifyTool').addClass('faded');
document.body.style.cursor = 'copy'; document.body.style.cursor = 'copy';
} else if (elemBelow.classList.contains('imageViewerViewport') && elemBelow.classList.contains('empty')) { } else if (elemBelow.classList.contains('imageViewerViewport') && elemBelow.classList.contains('empty')) {
// If we're dragging over an empty viewport, just change the cursor style // If we're dragging over an empty viewport, just change the cursor style
@ -128,28 +129,32 @@ function thumbnailDragHandler(e, target) {
// Otherwise, keep the cursor as no-drop style // Otherwise, keep the cursor as no-drop style
document.body.style.cursor = 'no-drop'; document.body.style.cursor = 'no-drop';
} }
} };
function thumbnailDragEndHandler(e, target) { const thumbnailDragEndHandler = (event, data, handlers) => {
// Remove the mouse event listeners // Remove the mouse event listeners
$(document).off('mousemove mouseup'); if (handlers) {
$(document).off('mousemove', handlers.mousemove);
$(document).off('mouseup', handlers.mouseup);
}
// Reset the cursor style to the default // Reset the cursor style to the default
document.body.style.cursor = 'auto'; document.body.style.cursor = 'auto';
// Get the cloned element // Get the cloned element
var clone = $('#' + target.id + 'DragClone'); const $clone = $('#DragClone');
// If it doesn't exist, stop here // If it doesn't exist, stop here
if (!clone.length) { if (!$clone.length) {
return; return;
} }
var top = clone.position().top; const top = $clone.offset().top;
var left = clone.position().left; const left = $clone.offset().left;
const diff = $clone.data('diff');
// Identify the element below the cloned element position // Identify the element below the cloned element position
var elemBelow = document.elementFromPoint(left + diffX, top + diffY); const elemBelow = document.elementFromPoint(left + diff.x, top + diff.y);
// Remove all cloned elements from the page // Remove all cloned elements from the page
$('.imageThumbnailClone').remove(); $('.imageThumbnailClone').remove();
@ -165,11 +170,11 @@ function thumbnailDragEndHandler(e, target) {
// Remove any fade effects on the element below // Remove any fade effects on the element below
elemBelow.classList.remove('faded'); elemBelow.classList.remove('faded');
var element; let element;
var viewportsDraggedOver = $(elemBelow).parents('.imageViewerViewport'); const $viewportsDraggedOver = $(elemBelow).closest('.imageViewerViewport');
if (viewportsDraggedOver.length) { if ($viewportsDraggedOver.length) {
// If we're dragging over a non-empty viewport, retrieve it // If we're dragging over a non-empty viewport, retrieve it
element = viewportsDraggedOver.get(0); element = $viewportsDraggedOver.get(0);
} else if (elemBelow.classList.contains('imageViewerViewport') && } else if (elemBelow.classList.contains('imageViewerViewport') &&
elemBelow.classList.contains('empty')) { elemBelow.classList.contains('empty')) {
// If we're dragging over an empty viewport, retrieve that instead // If we're dragging over an empty viewport, retrieve that instead
@ -179,48 +184,54 @@ function thumbnailDragEndHandler(e, target) {
return false; return false;
} }
// If there is no store drag and drop data, stop here // If there is no stored drag and drop data, stop here
if (!OHIF.viewer.dragAndDropData) { if (!data) {
return false; return false;
} }
var viewportIndex = $('.imageViewerViewport').index(element); // Get the dropped viewport index
const viewportIndex = $('.imageViewerViewport').index(element);
// Removes active class from previous selected series in viewport // Rerender the viewport using the dragged thumbnail data
const previousUid = layoutManager.viewportData[viewportIndex].seriesInstanceUid; layoutManager.rerenderViewportWithNewSeries(viewportIndex, data);
if (previousUid) {
$(`.thumbnailEntry[data-uid="${previousUid}"]`).removeClass('active');
}
// Rerender the viewport using the drag and drop data
layoutManager.rerenderViewportWithNewSeries(viewportIndex, OHIF.viewer.dragAndDropData);
// Set active state for dragged thumbnail entry
const newUid = OHIF.viewer.dragAndDropData.seriesInstanceUid
$(`.thumbnailEntry[data-uid="${newUid}"]`).addClass('active');
return false; return false;
} };
Template.thumbnailEntry.onRendered(function() { Template.thumbnailEntry.onCreated(() => {
var entry = this.find('.thumbnailEntry'); const instance = Template.instance();
$(entry).data('seriesInstanceUid', Template.parentData(0).seriesInstanceUid);
$(entry).data('studyInstanceUid', Template.parentData(1).studyInstanceUid); // Check if the thumbnails will be draggable or clickable
instance.isDragAndDrop = _.isUndefined(instance.data.viewportIndex);
}); });
Template.thumbnailEntry.events({ Template.thumbnailEntry.events({
// Touch drag/drop events // Event handlers for drag and drop
'touchstart .thumbnailEntry, mousedown .thumbnailEntry': function(e) { 'touchstart .thumbnailEntry, mousedown .thumbnailEntry'(event, instance) {
var data = { const data = instance.data.thumbnail.stack;
studyInstanceUid: this.stack.studyInstanceUid, instance.isDragAndDrop && thumbnailDragStartHandler(event, data);
seriesInstanceUid: this.stack.seriesInstanceUid
};
thumbnailDragStartHandler(e, data);
}, },
'touchmove .thumbnailEntry': function(e) { 'touchmove .thumbnailEntry'(event, instance) {
thumbnailDragHandler(e, e.currentTarget); instance.isDragAndDrop && thumbnailDragHandler(event);
}, },
'touchend .thumbnailEntry': function(e) { 'touchend .thumbnailEntry'(event, instance) {
thumbnailDragEndHandler(e, e.currentTarget); const data = instance.data.thumbnail.stack;
instance.isDragAndDrop && thumbnailDragEndHandler(event, data);
},
// Event handlers for click (quick switch)
'click .thumbnailEntry'(event, instance) {
if (instance.isDragAndDrop) {
return;
}
const data = instance.data.thumbnail.stack;
// Rerender the viewport using the clicked thumbnail data
layoutManager.rerenderViewportWithNewSeries(instance.data.viewportIndex, data);
}
});
Template.thumbnailEntry.helpers({
draggableClass() {
return Template.instance().isDragAndDrop ? 'draggable' : '';
} }
}); });

View File

@ -3,12 +3,15 @@
$seriesCountBackgroundColor = #678696 $seriesCountBackgroundColor = #678696
.thumbnailEntry .thumbnailEntry
cursor: copy cursor: pointer
cursor: -webkit-grab
cursor: -moz-grab
display: table display: table
margin: 0 auto
font-family: Roboto font-family: Roboto
margin: 0 auto
&.draggable
cursor: copy
cursor: -webkit-grab
cursor: -moz-grab
.seriesDetails .seriesDetails
color: $textPrimaryColor color: $textPrimaryColor

View File

@ -0,0 +1,29 @@
/**
* Boolean helper to identify if a series instance is active in some viewport
*/
Template.registerHelper('isSeriesActive', (seriesInstanceUid, viewportIndex) => {
// Register dependency on Session key that is changed
// when viewports are updated
Session.get('LayoutManagerUpdated');
// Stop here if layoutManager is not defined yet
if (!layoutManager) {
return;
}
let viewportData;
if (_.isUndefined(viewportIndex)) {
viewportData = layoutManager.viewportData;
} else {
viewportData = [layoutManager.viewportData[viewportIndex]];
}
let result = false;
_.each(viewportData, data => {
if (data.seriesInstanceUid === seriesInstanceUid) {
result = true;
}
});
return result;
});

View File

@ -173,21 +173,24 @@ Package.onUse(function(api) {
api.export('ViewerStudies', 'client'); api.export('ViewerStudies', 'client');
// UI Helpers // UI Helpers
api.addFiles('lib/helpers/formatDA.js', 'client'); api.addFiles([
api.addFiles('lib/helpers/logical.js', 'client'); 'lib/helpers/formatDA.js',
api.addFiles('lib/helpers/formatJSDate.js', 'client'); 'lib/helpers/logical.js',
api.addFiles('lib/helpers/jsDateFromNow.js', 'client'); 'lib/helpers/formatJSDate.js',
api.addFiles('lib/helpers/formatNumberPrecision.js', 'client'); 'lib/helpers/jsDateFromNow.js',
api.addFiles('lib/helpers/formatTM.js', 'client'); 'lib/helpers/formatNumberPrecision.js',
api.addFiles('lib/helpers/inlineIf.js', 'client'); 'lib/helpers/formatTM.js',
api.addFiles('lib/helpers/inc.js', 'client'); 'lib/helpers/inlineIf.js',
api.addFiles('lib/helpers/getUsername.js', 'client'); 'lib/helpers/inc.js',
api.addFiles('lib/helpers/capitalizeFirstLetter.js', 'client'); 'lib/helpers/isSeriesActive.js',
api.addFiles('lib/helpers/objectToPairs.js', 'client'); 'lib/helpers/getUsername.js',
api.addFiles('lib/helpers/objectEach.js', 'client'); 'lib/helpers/capitalizeFirstLetter.js',
api.addFiles('lib/helpers/ifTypeIs.js', 'client'); 'lib/helpers/objectToPairs.js',
api.addFiles('lib/helpers/prettyPrintStringify.js', 'client'); 'lib/helpers/objectEach.js',
api.addFiles('lib/helpers/formatPN.js', 'client'); 'lib/helpers/ifTypeIs.js',
'lib/helpers/prettyPrintStringify.js',
'lib/helpers/formatPN.js'
], 'client');
api.export('formatPN', 'client'); api.export('formatPN', 'client');
api.addFiles('lib/helpers/isTouchDevice.js', 'client'); api.addFiles('lib/helpers/isTouchDevice.js', 'client');