Work-in-progress on study/timepoint association modal and multi-study selection (LT-60, LT-116)
This commit is contained in:
parent
10be1b0336
commit
41b75de814
@ -18,8 +18,18 @@ Router.configure({
|
|||||||
|
|
||||||
Router.onBeforeAction('loading');
|
Router.onBeforeAction('loading');
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
additionalTemplates: [
|
||||||
|
'associationModal'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
var routerOptions = {
|
||||||
|
data: data
|
||||||
|
};
|
||||||
|
|
||||||
Router.route('/', function() {
|
Router.route('/', function() {
|
||||||
this.render('worklist');
|
this.render('worklist', routerOptions);
|
||||||
});
|
});
|
||||||
|
|
||||||
Router.route('/viewer/:_id', {
|
Router.route('/viewer/:_id', {
|
||||||
@ -39,7 +49,7 @@ Router.route('/viewer/:_id', {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.render('worklist');
|
this.render('worklist', routerOptions);
|
||||||
openNewTab(studyInstanceUid);
|
openNewTab(studyInstanceUid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
<template name="associationModal">
|
||||||
|
<div class="modal" id="associationModal" tabindex="-1" role="dialog" aria-labelledby="associationModalLabel">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="associationModalLabel">Study Association</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{{ >studyAssociationTable }}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" id="saveAssociations" data-dismiss="modal" data-toggle="modal" data-target="#associationModal">Save</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
Template.associationModal.events({
|
||||||
|
'click #saveAssociations': function(e) {
|
||||||
|
log.info("Saving associations");
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
#associationModal
|
||||||
|
.modal-dialog
|
||||||
|
width: 80%
|
||||||
@ -12,6 +12,12 @@ function closeHandler() {
|
|||||||
setFocusToActiveViewport();
|
setFocusToActiveViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the confirmation dialog template and the removable backdrop element
|
||||||
|
*
|
||||||
|
* @param doneCallback A callback
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
showConfirmDialog = function(doneCallback, options) {
|
showConfirmDialog = function(doneCallback, options) {
|
||||||
// Show the backdrop
|
// Show the backdrop
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|||||||
@ -24,9 +24,6 @@ function setLesionNumberCallback(measurementData, eventData, doneCallback) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get patientId
|
|
||||||
// TODO: add measurement data according to patientId to get correct lesion number for each patient
|
|
||||||
|
|
||||||
measurementData.timepointID = timepoint.timepointID;
|
measurementData.timepointID = timepoint.timepointID;
|
||||||
|
|
||||||
// Get a lesion number for this lesion, depending on whether or not the same lesion previously
|
// Get a lesion number for this lesion, depending on whether or not the same lesion previously
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
<template name="studyAssociationTable">
|
||||||
|
<div id="studyAssociationTable">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h4>Instructions</h4>
|
||||||
|
<p>Associate the selected studies with timepoints in the clinical trial.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="center">Include Study?</th>
|
||||||
|
<th class="center">Study Date</th>
|
||||||
|
<th class="center">Study Description</th>
|
||||||
|
<th class="center">Timepoint Type</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{{ #each relevantStudies }}
|
||||||
|
<tr>
|
||||||
|
<td class="center">
|
||||||
|
<input type="checkbox" class="includeStudy" checked/>
|
||||||
|
</td>
|
||||||
|
<td class="center">
|
||||||
|
{{ #if autoselected}}
|
||||||
|
<p class="studyDate autoselected"
|
||||||
|
title="This study was automatically added to your list due to its
|
||||||
|
similarity with your other selected studies">
|
||||||
|
{{formatDA studyDate}}
|
||||||
|
</p>
|
||||||
|
{{ else }}
|
||||||
|
<p class="studyDate">{{formatDA studyDate}}</p>
|
||||||
|
{{ /if }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>{{studyDescription}}</p>
|
||||||
|
</td>
|
||||||
|
<td class="timepointOptions center">
|
||||||
|
{{ #each timepointOptions }}
|
||||||
|
<label><input type="radio" name="{{_id}}" value={{type}}> {{name}}</label>
|
||||||
|
{{ /each }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ /each }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
function autoSelectStudies() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Template.studyAssociationTable.helpers({
|
||||||
|
/**
|
||||||
|
* This helpers includes the user-selected and autoselected studies
|
||||||
|
* to be associated.
|
||||||
|
*
|
||||||
|
* @returns {Array.<T>}
|
||||||
|
*/
|
||||||
|
relevantStudies: function() {
|
||||||
|
var userSelectedStudies = WorklistSelectedStudies.find().fetch() || [];
|
||||||
|
var autoselected = autoSelectStudies(userSelectedStudies);
|
||||||
|
return userSelectedStudies.concat(autoselected);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* This helper returns the list of Timepoint types the user can set for this study
|
||||||
|
*
|
||||||
|
* @returns {Array.<T>}
|
||||||
|
*/
|
||||||
|
timepointOptions: function() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: 'baseline',
|
||||||
|
name: 'Baseline'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'followup',
|
||||||
|
name: 'Follow-up'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//trial criteria!
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
#studyAssociationTable
|
||||||
|
.header
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
.center
|
||||||
|
text-align: center
|
||||||
|
|
||||||
|
table
|
||||||
|
tbody
|
||||||
|
tr
|
||||||
|
td.timepointOptions
|
||||||
|
input
|
||||||
|
margin: 0 3px
|
||||||
|
|
||||||
|
label
|
||||||
|
padding: 0 5px
|
||||||
@ -30,9 +30,9 @@ getLineIntersection = function(point1, point2, point3, point4) {
|
|||||||
* same side of line 1, the line segments do not intersect.
|
* same side of line 1, the line segments do not intersect.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (r3 != 0 &&
|
if (r3 !== 0 &&
|
||||||
r4 != 0 &&
|
r4 !== 0 &&
|
||||||
sign(r3) == sign(r4)) {
|
sign(r3) === sign(r4)) {
|
||||||
intersectionPoint.x = 0;
|
intersectionPoint.x = 0;
|
||||||
intersectionPoint.y = 0;
|
intersectionPoint.y = 0;
|
||||||
intersectionPoint.intersected = false;
|
intersectionPoint.intersected = false;
|
||||||
@ -55,9 +55,9 @@ getLineIntersection = function(point1, point2, point3, point4) {
|
|||||||
* not intersect.
|
* not intersect.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (r1 != 0 &&
|
if (r1 !== 0 &&
|
||||||
r2 != 0 &&
|
r2 !== 0 &&
|
||||||
sign(r1) == sign(r2)) {
|
sign(r1) === sign(r2)) {
|
||||||
intersectionPoint.x = 0;
|
intersectionPoint.x = 0;
|
||||||
intersectionPoint.y = 0;
|
intersectionPoint.y = 0;
|
||||||
intersectionPoint.intersected = false;
|
intersectionPoint.intersected = false;
|
||||||
@ -103,7 +103,7 @@ getDistanceFromPointToLine = function(ptTest, pt1, pt2) {
|
|||||||
var dy = pt2.y - pt1.y;
|
var dy = pt2.y - pt1.y;
|
||||||
|
|
||||||
// It's a point, not a line
|
// It's a point, not a line
|
||||||
if (dx == 0 && dy == 0) {
|
if (dx === 0 && dy === 0) {
|
||||||
ptNearest.x = pt1.x;
|
ptNearest.x = pt1.x;
|
||||||
ptNearest.y = pt1.y;
|
ptNearest.y = pt1.y;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -36,6 +36,10 @@ Package.onUse(function(api) {
|
|||||||
bare: true
|
bare: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
api.addFiles('client/components/associationModal/associationModal.html', 'client');
|
||||||
|
api.addFiles('client/components/associationModal/associationModal.styl', 'client');
|
||||||
|
api.addFiles('client/components/associationModal/associationModal.js', 'client');
|
||||||
|
|
||||||
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.html', 'client');
|
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.html', 'client');
|
||||||
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.js', 'client');
|
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.js', 'client');
|
||||||
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.styl', 'client');
|
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.styl', 'client');
|
||||||
@ -63,6 +67,10 @@ Package.onUse(function(api) {
|
|||||||
api.addFiles('client/components/studyDateList/studyDateList.styl', 'client');
|
api.addFiles('client/components/studyDateList/studyDateList.styl', 'client');
|
||||||
api.addFiles('client/components/studyDateList/studyDateList.js', 'client');
|
api.addFiles('client/components/studyDateList/studyDateList.js', 'client');
|
||||||
|
|
||||||
|
api.addFiles('client/components/studyAssociationTable/studyAssociationTable.html', 'client');
|
||||||
|
api.addFiles('client/components/studyAssociationTable/studyAssociationTable.styl', 'client');
|
||||||
|
api.addFiles('client/components/studyAssociationTable/studyAssociationTable.js', 'client');
|
||||||
|
|
||||||
api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.html', 'client');
|
api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.html', 'client');
|
||||||
api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.styl', 'client');
|
api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.styl', 'client');
|
||||||
api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.js', 'client');
|
api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.js', 'client');
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
<template name="studyContextMenu">
|
||||||
|
<div id="studyContextMenu" class="studyContextMenu noselect"
|
||||||
|
oncontextmenu='return false;'
|
||||||
|
unselectable='on'
|
||||||
|
onselectstart='return false;'>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<!--<a id="deleteTool">Delete</a>-->
|
||||||
|
<!-- TODO: Make this dynamically accept options-->
|
||||||
|
<a id="launchStudyAssociation" type="button"
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#associationModal"
|
||||||
|
title="Launch Study Association">
|
||||||
|
<i class="fa fa-calendar-plus-o fa-lg"></i>
|
||||||
|
Associate
|
||||||
|
</a>
|
||||||
|
<a><span class="fa-stack fa-lg">
|
||||||
|
<i class="fa fa-user fa-stack-1x"></i>
|
||||||
|
<i class="fa fa-ban fa-stack-2x text-danger"></i>
|
||||||
|
</span> Anonymize
|
||||||
|
</a>
|
||||||
|
<a><i class="fa fa-trash fa-lg"></i> Delete</a>
|
||||||
|
<a><i class="fa fa-share fa-lg"></i> Share</a>
|
||||||
|
<a><i class="fa fa-download fa-lg"></i> Download</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
function closeHandler(dialog) {
|
||||||
|
// Hide the dialog
|
||||||
|
$(dialog).css('display', 'none');
|
||||||
|
|
||||||
|
// Remove the backdrop
|
||||||
|
$('.removableBackdrop').remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is used inside the Worklist package to define a right click callback
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
* @param template
|
||||||
|
*/
|
||||||
|
openStudyContextMenu = function(e, template) {
|
||||||
|
var study = $(e.currentTarget);
|
||||||
|
Template.studyContextMenu.study = study;
|
||||||
|
|
||||||
|
var dialog = $('#studyContextMenu');
|
||||||
|
|
||||||
|
// Show the nonTargetLesion dialog above
|
||||||
|
var dialogProperty = {
|
||||||
|
display: 'block'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Device is touch device or not
|
||||||
|
// If device is touch device, set position center of screen vertically and horizontally
|
||||||
|
if (isTouchDevice()) {
|
||||||
|
// add dialogMobile class to provide a black, transparent background
|
||||||
|
dialog.addClass('dialogMobile');
|
||||||
|
dialogProperty.top = 0;
|
||||||
|
dialogProperty.left = 0;
|
||||||
|
dialogProperty.right = 0;
|
||||||
|
dialogProperty.bottom = 0;
|
||||||
|
} else {
|
||||||
|
dialogProperty.top = e.pageY;// - dialog.outerHeight() - 40;
|
||||||
|
dialogProperty.left = e.pageX;// - dialog.outerWidth() / 2;
|
||||||
|
|
||||||
|
var pageHeight = $(window).height();
|
||||||
|
dialogProperty.top = Math.max(dialogProperty.top, 0);
|
||||||
|
dialogProperty.top = Math.min(dialogProperty.top, pageHeight - dialog.outerHeight());
|
||||||
|
|
||||||
|
var pageWidth = $(window).width();
|
||||||
|
dialogProperty.left = Math.max(dialogProperty.left, 0);
|
||||||
|
dialogProperty.left = Math.min(dialogProperty.left, pageWidth - dialog.outerWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.css(dialogProperty);
|
||||||
|
dialog.focus();
|
||||||
|
|
||||||
|
log.info(e);
|
||||||
|
log.info(template);
|
||||||
|
|
||||||
|
// Show the backdrop
|
||||||
|
UI.render(Template.removableBackdrop, document.body);
|
||||||
|
|
||||||
|
// Make sure the context menu is closed when the user clicks away
|
||||||
|
$('.removableBackdrop').one('mousedown touchstart', function() {
|
||||||
|
closeHandler(dialog);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Temporary for now
|
||||||
|
functionList = {};
|
||||||
|
|
||||||
|
Template.studyContextMenu.events({
|
||||||
|
'click a': function(e) {
|
||||||
|
var study = Template.studyContextMenu.study;
|
||||||
|
var id = $(e.currentTarget).attr('id');
|
||||||
|
|
||||||
|
var fn = functionList[id];
|
||||||
|
if (fn && typeof(fn) === 'function') {
|
||||||
|
fn(study);
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialog = $('#studyContextMenu');
|
||||||
|
closeHandler(dialog);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
.studyContextMenu
|
||||||
|
z-index: 10000
|
||||||
|
display: none
|
||||||
|
position: absolute
|
||||||
|
color: whitesmoke
|
||||||
|
background-color: #2d2d2d
|
||||||
|
|
||||||
|
width: -moz-fit-content
|
||||||
|
width: -webkit-fit-content
|
||||||
|
width: fit-content
|
||||||
|
|
||||||
|
height: -moz-fit-content
|
||||||
|
height: -webkit-fit-content
|
||||||
|
height: fit-content
|
||||||
|
|
||||||
|
min-width: 100px
|
||||||
|
font-size: 14px
|
||||||
|
text-align: left
|
||||||
|
-webkit-background-clip: padding-box
|
||||||
|
background-clip: padding-box
|
||||||
|
border: 1px solid rgba(0, 0, 0, .15)
|
||||||
|
border-radius: 4px
|
||||||
|
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175)
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, .175)
|
||||||
|
|
||||||
|
ul
|
||||||
|
width: 100%
|
||||||
|
margin: 5px 0
|
||||||
|
list-style: none
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
li
|
||||||
|
clear: both
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
|
a
|
||||||
|
padding: 3px 15px
|
||||||
|
display: block
|
||||||
|
text-decoration: none
|
||||||
|
cursor: pointer
|
||||||
|
line-height: 25px
|
||||||
|
color: #808080
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color: #439193
|
||||||
|
text-decoration: none
|
||||||
|
background-color: #1d1d1d
|
||||||
|
|
||||||
|
.fa-lg
|
||||||
|
width: 40px
|
||||||
|
text-align: center
|
||||||
@ -17,4 +17,8 @@
|
|||||||
{{ >tabContent }}
|
{{ >tabContent }}
|
||||||
{{ /each }}
|
{{ /each }}
|
||||||
</div>
|
</div>
|
||||||
|
{{#each additionalTemplates}}
|
||||||
|
{{> UI.dynamic template=this}}
|
||||||
|
{{/each}}
|
||||||
|
{{ >studyContextMenu }}
|
||||||
</template>
|
</template>
|
||||||
@ -65,17 +65,17 @@ getStudyMetadata = function(studyInstanceUid, doneCallback) {
|
|||||||
* @param contentId The unique ID of the tab to be switched to
|
* @param contentId The unique ID of the tab to be switched to
|
||||||
*/
|
*/
|
||||||
switchToTab = function(contentId) {
|
switchToTab = function(contentId) {
|
||||||
log.info("Switching to tab: " + contentId);
|
log.info('Switching to tab: ' + contentId);
|
||||||
|
|
||||||
// Use Bootstrap's Tab JavaScript to show the contents of the current tab
|
// Use Bootstrap's Tab JavaScript to show the contents of the current tab
|
||||||
// Unless it is the worklist, it is currently an empty div
|
// Unless it is the worklist, it is currently an empty div
|
||||||
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
|
$('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
|
||||||
|
|
||||||
// Remove any previous Viewers from the DOM
|
// Remove any previous Viewers from the DOM
|
||||||
$("#viewer").remove();
|
$('#viewer').remove();
|
||||||
|
|
||||||
// Update the 'activeContentId' variable in Session
|
// Update the 'activeContentId' variable in Session
|
||||||
Session.set("activeContentId", contentId);
|
Session.set('activeContentId', contentId);
|
||||||
|
|
||||||
// If we are switching to the Worklist tab, reset any CSS styles
|
// If we are switching to the Worklist tab, reset any CSS styles
|
||||||
// that have been applied to prevent scrolling in the Viewer.
|
// that have been applied to prevent scrolling in the Viewer.
|
||||||
@ -90,7 +90,7 @@ switchToTab = function(contentId) {
|
|||||||
|
|
||||||
// Get tab content container given the contentId string
|
// Get tab content container given the contentId string
|
||||||
// If no such container exists, stop here because something is wrong
|
// If no such container exists, stop here because something is wrong
|
||||||
var container = $('.tab-content').find("#" + contentId).get(0);
|
var container = $('.tab-content').find('#' + contentId).get(0);
|
||||||
if (!container) {
|
if (!container) {
|
||||||
log.warn('No container present with the contentId: ' + contentId);
|
log.warn('No container present with the contentId: ' + contentId);
|
||||||
return;
|
return;
|
||||||
@ -117,18 +117,18 @@ switchToTab = function(contentId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the loading text template that is inside the tab container by default
|
// Remove the loading text template that is inside the tab container by default
|
||||||
container.innerHTML = "";
|
container.innerHTML = '';
|
||||||
|
|
||||||
// Use Blaze to render the Viewer Template into the container
|
// Use Blaze to render the Viewer Template into the container
|
||||||
UI.renderWithData(Template.viewer, data, container);
|
UI.renderWithData(Template.viewer, data, container);
|
||||||
|
|
||||||
// Retrieve the DOM element of the viewer
|
// Retrieve the DOM element of the viewer
|
||||||
var imageViewer = $("#viewer");
|
var imageViewer = $('#viewer');
|
||||||
|
|
||||||
// If it is present in the DOM (it should be), then apply
|
// If it is present in the DOM (it should be), then apply
|
||||||
// styles to prevent page scrolling and overscrolling on mobile devices
|
// styles to prevent page scrolling and overscrolling on mobile devices
|
||||||
if (imageViewer) {
|
if (imageViewer) {
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = 'hidden';
|
||||||
document.body.style.height = '100%';
|
document.body.style.height = '100%';
|
||||||
document.body.style.width = '100%';
|
document.body.style.width = '100%';
|
||||||
document.body.style.minWidth = 0;
|
document.body.style.minWidth = 0;
|
||||||
@ -171,21 +171,20 @@ openNewTab = function(studyInstanceUid, title) {
|
|||||||
Template.worklist.onRendered(function() {
|
Template.worklist.onRendered(function() {
|
||||||
// If there is a tab set as active in the Session,
|
// If there is a tab set as active in the Session,
|
||||||
// switch to that now.
|
// switch to that now.
|
||||||
var contentId = Session.get("activeContentId");
|
var contentId = Session.get('activeContentId');
|
||||||
if (contentId) {
|
if (contentId) {
|
||||||
switchToTab(contentId);
|
switchToTab(contentId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Template.worklist.helpers({
|
Template.worklist.helpers({
|
||||||
/**
|
/**
|
||||||
* Returns the current set of Worklist Tabs
|
* Returns the current set of Worklist Tabs
|
||||||
* @returns Meteor.Collection The current state of the WorklistTabs Collection
|
* @returns Meteor.Collection The current state of the WorklistTabs Collection
|
||||||
*/
|
*/
|
||||||
'worklistTabs': function() {
|
worklistTabs: function() {
|
||||||
return WorklistTabs.find();
|
return WorklistTabs.find();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.worklist.events({
|
Template.worklist.events({
|
||||||
@ -193,12 +192,12 @@ Template.worklist.events({
|
|||||||
// If this tab is already active, do nothing
|
// If this tab is already active, do nothing
|
||||||
var tabButton = $(e.currentTarget);
|
var tabButton = $(e.currentTarget);
|
||||||
var tabTitle = tabButton.parents('.tabTitle');
|
var tabTitle = tabButton.parents('.tabTitle');
|
||||||
if (tabTitle.hasClass("active")) {
|
if (tabTitle.hasClass('active')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, switch to the tab
|
// Otherwise, switch to the tab
|
||||||
var contentId = tabButton.data('target').replace("#", "");
|
var contentId = tabButton.data('target').replace('#', '');
|
||||||
switchToTab(contentId);
|
switchToTab(contentId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -8,7 +8,12 @@ Template.worklistResult.helpers({
|
|||||||
* by Patient name and Study Date in Ascending order.
|
* by Patient name and Study Date in Ascending order.
|
||||||
*/
|
*/
|
||||||
studies: function() {
|
studies: function() {
|
||||||
return WorklistStudies.find({}, {sort: {patientName : 1, studyDate : 1}});
|
return WorklistStudies.find({}, {
|
||||||
|
sort: {
|
||||||
|
patientName: 1,
|
||||||
|
studyDate: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
isTouchDevice: function() {
|
isTouchDevice: function() {
|
||||||
@ -36,6 +41,7 @@ function getFilter(filter) {
|
|||||||
if (filter && filter.length && filter.substr(filter.length - 1) !== '*') {
|
if (filter && filter.length && filter.substr(filter.length - 1) !== '*') {
|
||||||
filter += '*';
|
filter += '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +52,7 @@ function isIndexOf(mainVal, searchVal) {
|
|||||||
if (mainVal === undefined || mainVal === '' || mainVal.indexOf(searchVal) > -1){
|
if (mainVal === undefined || mainVal === '' || mainVal.indexOf(searchVal) > -1){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,8 +60,8 @@ function isIndexOf(mainVal, searchVal) {
|
|||||||
* Replace object if undefined
|
* Replace object if undefined
|
||||||
*/
|
*/
|
||||||
function replaceUndefinedColumnValue (text) {
|
function replaceUndefinedColumnValue (text) {
|
||||||
if (text == undefined || text === "undefined") {
|
if (text == undefined || text === 'undefined') {
|
||||||
return "";
|
return '';
|
||||||
} else {
|
} else {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
@ -67,7 +74,7 @@ function convertStringToStudyDate (dateStr) {
|
|||||||
var y = dateStr.substring(0,4);
|
var y = dateStr.substring(0,4);
|
||||||
var m = dateStr.substring(4,6);
|
var m = dateStr.substring(4,6);
|
||||||
var d = dateStr.substring(6,8);
|
var d = dateStr.substring(6,8);
|
||||||
var newDateStr = y+"/"+m+"/"+d;
|
var newDateStr = y + '/' + m + '/' + d;
|
||||||
return new Date(newDateStr);
|
return new Date(newDateStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,13 +118,12 @@ function search() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Template.worklistResult.events({
|
Template.worklistResult.events({
|
||||||
'keydown': function(event) {
|
'keydown input': function(event) {
|
||||||
if (event.keyCode === 13) { // Enter
|
if (event.keyCode === 13) { // Enter
|
||||||
search();
|
search();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'onsearch': function(event) {
|
'onsearch input': function(event) {
|
||||||
search();
|
search();
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -10,19 +10,24 @@ table#tblStudyList
|
|||||||
&:hover
|
&:hover
|
||||||
color: #888888
|
color: #888888
|
||||||
|
|
||||||
tbody > tr
|
tbody
|
||||||
|
tr
|
||||||
padding: 4px
|
padding: 4px
|
||||||
border: 1px solid #282828
|
border: 1px solid #282828
|
||||||
background-color: black
|
background-color: black
|
||||||
|
|
||||||
|
&:hover, &:active, &.active
|
||||||
|
background-color: #009BD2
|
||||||
|
color: white
|
||||||
|
|
||||||
td
|
td
|
||||||
white-space:nowrap
|
// This selector is necessary to override bootstrap's 'table' class
|
||||||
|
color: white
|
||||||
|
background-color: #009BD2
|
||||||
|
|
||||||
&:nth-of-type(odd)
|
&:nth-of-type(odd)
|
||||||
background-color: #202020
|
background-color: #202020
|
||||||
|
|
||||||
&:hover
|
td
|
||||||
background-color: #009BD2
|
white-space: nowrap
|
||||||
color: white
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
{{accessionNumber}}
|
{{accessionNumber}}
|
||||||
</td>
|
</td>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{{formatDA studyDate}}
|
{{formatDA studyDate}}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -1,8 +1,129 @@
|
|||||||
|
Worklist = {};
|
||||||
|
Worklist.previouslySelected = undefined;
|
||||||
|
|
||||||
|
// Maybe we should use regular Worklist collection?
|
||||||
|
WorklistSelectedStudies = new Meteor.Collection(null);
|
||||||
|
|
||||||
|
function handleShiftClick(studyRow, data) {
|
||||||
|
log.info('shiftKey');
|
||||||
|
var studyInstanceUid = studyRow.attr('studyInstanceUid');
|
||||||
|
|
||||||
|
// Select all rows in between these two rows
|
||||||
|
if (Worklist.previouslySelected) {
|
||||||
|
var previous = $(Worklist.previouslySelected);
|
||||||
|
var rowsInBetween;
|
||||||
|
if (previous.index() < studyRow.index()) {
|
||||||
|
// The previously selected row is above (lower index) the
|
||||||
|
// currently selected row.
|
||||||
|
|
||||||
|
// Fill in the rows upwards from the previously selected row
|
||||||
|
rowsInBetween = previous.nextAll('tr');
|
||||||
|
} else if (previous.index() > studyRow.index()) {
|
||||||
|
// The previously selected row is below the currently
|
||||||
|
// selected row.
|
||||||
|
|
||||||
|
// Fill in the rows upwards from the previously selected row
|
||||||
|
rowsInBetween = previous.prevAll('tr');
|
||||||
|
} else {
|
||||||
|
// The rows are the same, deselect the current row.
|
||||||
|
// TODO: CHECK THIS, pretty sure this is the wrong behaviour
|
||||||
|
WorklistSelectedStudies.remove({});
|
||||||
|
Worklist.previouslySelected = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through the rows in between current and previous selected studies
|
||||||
|
rowsInBetween.each(function(index, row) {
|
||||||
|
if ($(row).is(studyRow)) {
|
||||||
|
// When we reach the currently clicked-on row, stop
|
||||||
|
return false;
|
||||||
|
} else if ($(row).hasClass('active')) {
|
||||||
|
// If we find one that is already selected, do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the relevant studyInstanceUid
|
||||||
|
var studyInstanceUid = $(row).attr('studyInstanceUid');
|
||||||
|
|
||||||
|
// Set the current study as selected
|
||||||
|
WorklistSelectedStudies.insert(data);
|
||||||
|
studyRow.addClass('active');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Set the current study as selected
|
||||||
|
WorklistSelectedStudies.insert(data);
|
||||||
|
studyRow.addClass('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCtrlClick(studyRow, data) {
|
||||||
|
log.info('ctrlKey');
|
||||||
|
var studyInstanceUid = studyRow.attr('studyInstanceUid');
|
||||||
|
|
||||||
|
if (studyRow.hasClass('active')) {
|
||||||
|
studyRow.removeClass('active');
|
||||||
|
|
||||||
|
// Find the current studyInstanceUid in the stored list and remove it
|
||||||
|
WorklistSelectedStudies.remove({
|
||||||
|
studyInstanceUid: data.studyInstanceUid
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Set the current study as selected
|
||||||
|
WorklistSelectedStudies.insert(data);
|
||||||
|
studyRow.addClass('active');
|
||||||
|
|
||||||
|
// Set this as the previously selected row, so the user can
|
||||||
|
// use Shift to select from this point onwards
|
||||||
|
Worklist.previouslySelected = studyRow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Template.worklistStudy.events({
|
Template.worklistStudy.events({
|
||||||
'click': function () {
|
'click tr.worklistStudy': function(e) {
|
||||||
|
var studyRow = $(e.currentTarget);
|
||||||
|
var data = this;
|
||||||
|
|
||||||
|
// Remove the ID so we can directly insert this into our client-side collection
|
||||||
|
delete data._id;
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
handleShiftClick(studyRow, data);
|
||||||
|
} else if (e.ctrlKey || e.metaKey) {
|
||||||
|
handleCtrlClick(studyRow, data);
|
||||||
|
} else {
|
||||||
|
// Select a single study
|
||||||
|
log.info('Regular click');
|
||||||
|
|
||||||
|
// Clear all selected studies
|
||||||
|
WorklistSelectedStudies.remove({});
|
||||||
|
$('tr.worklistStudy').removeClass('active');
|
||||||
|
|
||||||
|
// Set the previous study to the currently clicked-on study
|
||||||
|
Worklist.previouslySelected = studyRow;
|
||||||
|
|
||||||
|
// Set the current study as selected
|
||||||
|
WorklistSelectedStudies.insert(data);
|
||||||
|
studyRow.addClass('active');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'dblclick tr.worklistStudy': function() {
|
||||||
// Use the formatPN template helper to clean up the patient name
|
// Use the formatPN template helper to clean up the patient name
|
||||||
var title = Blaze._globalHelpers['formatPN'](this.patientName);
|
var title = Blaze._globalHelpers['formatPN'](this.patientName);
|
||||||
|
|
||||||
|
// Open a new tab with this study
|
||||||
openNewTab(this.studyInstanceUid, title);
|
openNewTab(this.studyInstanceUid, title);
|
||||||
|
},
|
||||||
|
'contextmenu tr.worklistStudy': function(e, template) {
|
||||||
|
$(e.currentTarget).addClass('active');
|
||||||
|
|
||||||
|
if (openStudyContextMenu && typeof openStudyContextMenu === 'function') {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
openStudyContextMenu(e, template);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,10 @@ Package.onUse(function (api) {
|
|||||||
api.addFiles('components/worklistResult/worklistResult.js', 'client');
|
api.addFiles('components/worklistResult/worklistResult.js', 'client');
|
||||||
api.addFiles('components/worklistResult/worklistResult.styl', 'client');
|
api.addFiles('components/worklistResult/worklistResult.styl', 'client');
|
||||||
|
|
||||||
|
api.addFiles('components/studyContextMenu/studyContextMenu.html', 'client');
|
||||||
|
api.addFiles('components/studyContextMenu/studyContextMenu.js', 'client');
|
||||||
|
api.addFiles('components/studyContextMenu/studyContextMenu.styl', 'client');
|
||||||
|
|
||||||
api.addFiles('lib/generateUUID.js', 'client');
|
api.addFiles('lib/generateUUID.js', 'client');
|
||||||
api.export('generateUUID', 'client');
|
api.export('generateUUID', 'client');
|
||||||
|
|
||||||
@ -56,5 +60,6 @@ Package.onUse(function (api) {
|
|||||||
// Export the Collections
|
// Export the Collections
|
||||||
api.export('WorklistTabs', 'client');
|
api.export('WorklistTabs', 'client');
|
||||||
api.export('WorklistStudies', 'client');
|
api.export('WorklistStudies', 'client');
|
||||||
|
api.export('WorklistSelectedStudies', 'client');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user