PWV-1: Refactoring ohif:studylist package

This commit is contained in:
Bruno Alves de Faria authored and Erik Ziegler committed 2016-11-15 08:24:01 +01:00
1 parent 984ef94403
commit 4d79bf84a8
79 files changed
+450 -551

No files matched your search

@@ -0,0 +1,19 @@
import './studylist.html';
import './studylist.js';
import './studylist.styl';
import './studylistStudy/studylistStudy.html';
import './studylistStudy/studylistStudy.js';
import './studylistStudy/studylistStudy.styl';
import './studylistResult/studylistResult.html';
import './studylistResult/studylistResult.js';
import './studylistResult/studylistResult.styl';
import './studylistToolbar/studylistToolbar.html';
import './studylistToolbar/studylistToolbar.js';
import './studylistToolbar/studylistToolbar.styl';
import './studylistPagination/studylistPagination.html';
import './studylistPagination/studylistPagination.styl';
import './studylistPagination/studylistPagination.js';
@@ -0,0 +1,18 @@
<template name="studylist">
<div id="studylistTabs" class="tab-content">
<div role="tabpanel" class="tab-pane active" id="studylistTab">
<div class="studylistContainer">
{{> studylistResult (clone this)}}
</div>
</div>
<div role="tabpanel" class="tab-pane" id="viewerTab">
<div class="viewerContainer">
</div>
</div>
{{>loadingText}}
</div>
{{#each templateName in additionalTemplates}}
{{> UI.dynamic template=templateName}}
{{/each}}
{{>progressDialog}}
</template>
@@ -0,0 +1,46 @@
/**
* Template: StudyList
*
* This is the main component of the StudyList package
*/
// Define the ViewerData global object
// If there is currently any Session data for this object,
// use this to repopulate the variable
ViewerData = Session.get('ViewerData') || {};
// Define the StudyListStudies Collection
// This is a client-side only Collection which
// Stores the list of studies in the StudyList
StudyListStudies = new Meteor.Collection(null);
StudyListStudies._debugName = 'StudyListStudies';
Session.setDefault('activeContentId', 'studylistTab');
Template.studylist.onCreated(() => {
const instance = Template.instance();
if (StudyList.subscriptions) {
StudyList.subscriptions.forEach(subscriptionName => {
instance.subscribe(subscriptionName);
});
}
});
Template.studylist.onRendered(() => {
const instance = Template.instance();
if (instance.data && instance.data.studyInstanceUid) {
const studyInstanceUid = instance.data.studyInstanceUid;
openNewTab(studyInstanceUid, studyInstanceUid);
} else {
// If there is a tab set as active in the Session,
// switch to that now.
console.log('studylist onRendered');
const contentId = Session.get('activeContentId');
if (contentId !== 'studylistTab' && ViewerData && ViewerData[contentId]) {
switchToTab(contentId);
}
}
Meteor.subscribe('hangingprotocols');
});
@@ -0,0 +1,34 @@
@import "{design}/app"
body
theme('background-color', '$primaryBackgroundColor')
#tblStudyList
tr
height: 20px
#studylistTab
theme('background-color', '$primaryBackgroundColor')
.studylistContainer
theme('background-color', '$primaryBackgroundColor')
margin: 0 auto
color: white
float: left;
width: 100%;
.tab-content
width: 100%
height: calc(100% - 91px)
.loadingTextDiv
theme('color', '$textSecondaryColor')
font-size: 30px
.tab-pane
width: 100%
height: 100%
.viewerContainer
height: 100%
width: 100%
@@ -0,0 +1,29 @@
<template name="studylistPagination">
<div class="studylistPagination">
<div class="row">
<div class="col-xs-4 col-sm-3 col-md-3">
{{#with recordCount}}
<div class="form-inline form-group rows-per-page">
<label>
<span>Show</span>
<select class="js-select-rows-per-page">
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span class="rows-per-page-label">rows per page</span>
</label>
</div>
{{/with}}
</div>
<div class="col-xs-8 col-sm-9 col-md-9">
<div class="form-inline form-group page-number">
<label>
<ul id="pagination" class="no-margins"></ul>
</label>
</div>
</div>
</div>
</div>
</template>
@@ -0,0 +1,58 @@
import { Template } from 'meteor/templating';
import './studylistPagination.html';
const visiblePages = 10;
Template.studylistPagination.onCreated(function() {
let instance = Template.instance();
// replace parentVariable with the name of the instance variable
instance.parent = this.parent(1);
});
Template.studylistPagination.onRendered(function() {
const instance = Template.instance();
const $paginationControl = instance.$('#pagination');
// Track changes on recordCount and rowsPerPage
instance.autorun(function() {
const recordCount = instance.parent.recordCount.get();
const rowsPerPage = instance.parent.rowsPerPage.get();
// Destroy plugin if exists
if ($paginationControl.data().twbsPagination) {
$paginationControl.twbsPagination('destroy');
}
if (recordCount && rowsPerPage) {
const totalPageNumber = Math.ceil(recordCount / rowsPerPage);
// Initialize plugin
$paginationControl.twbsPagination({
totalPages: totalPageNumber,
visiblePages: visiblePages,
onPageClick: function (event, page) {
// Update currentPage
// Decrease page by 1 to set currentPage
// Since reactive table current page index starts by 0
instance.parent.currentPage.set(page - 1);
}
});
}
});
});
Template.studylistPagination.helpers({
recordCount() {
const instance = Template.instance();
return instance.parent.recordCount.get();
}
});
Template.studylistPagination.events({
'change .js-select-rows-per-page'(event, instance) {
const rowsPerPage = $(event.currentTarget).val();
// Update rowsPerPage of parent
instance.parent.rowsPerPage.set(parseInt(rowsPerPage, 10));
}
});
@@ -0,0 +1,43 @@
@import "{design}/app"
.studylistPagination
font-size: 13px
font-weight: normal !important
.rows-per-page
padding-top: 20px
label
font-weight: normal
select
theme('background-color', '$primaryBackgroundColor')
color: white
.page-number
text-align: right
label
font-weight: normal
ul#pagination
li
a
padding: 4px 8px
theme('background-color', '$primaryBackgroundColor')
color: white
&:hover
theme('color', '$activeColor')
.active
a
theme('background-color', '$uiGray')
border-color: #ddd
color: white
.disabled
a:hover
color: inherit
@@ -0,0 +1,107 @@
<template name="studylistResult">
<div class="studyListToolbar">
<div class="header">
Study list
</div>
<div class="studyCount pull-right">
{{numberOfStudies}}
</div>
<div class="pull-right">
{{>studylistToolbar}}
</div>
</div>
<div class="theadBackground">
</div>
<div id="studyListContainer">
<table id="tblStudyList" class="studylistResult table noselect">
<thead>
<tr>
<th class="patientName">
<div id="_patientName" class="sortingCell">
<span>Patient Name</span>
<i class="{{sortingColumnsIcons.patientName}}">&nbsp;</i>
</div>
<input type="text"
class="form-control studylist-search"
id="patientName"
placeholder="Patient Name">
</th>
<th class="patientId">
<div id="_patientId" class="sortingCell">
<span>MRN</span>
<i class="{{sortingColumnsIcons.patientId}}">&nbsp;</i>
</div>
<input type="text"
class="form-control studylist-search"
id="patientId"
placeholder="MRN">
</th>
<th class="accessionNumber">
<div id="_accessionNumber" class="sortingCell ">
<span>Accession #</span>
<i class="{{sortingColumnsIcons.accessionNumber}}">&nbsp;</i>
</div>
<input type="text"
class="form-control studylist-search"
id="accessionNumber"
placeholder="Accession #">
</th>
<th class="studyDate">
<div id="_studyDate" class="sortingCell">
<span>Study Date</span>
<i class="{{sortingColumnsIcons.studyDate}}">&nbsp;</i>
</div>
<input type="text"
class="form-control studylist-search"
name="daterange"
id="studyDate"
placeholder="Study Date">
</th>
<th class="modalities">
<div id="_modalities" class="sortingCell">
<span>Modality</span>
<i class="{{sortingColumnsIcons.modalities}}">&nbsp;</i>
</div>
<input type="text"
class="form-control studylist-search"
id="modality"
placeholder="Modality">
</th>
<th class="studyDescription">
<div id="_studyDescription" class="sortingCell">
<span>Study Description</span>
<i class="{{sortingColumnsIcons.studyDescription}}">&nbsp;</i>
</div>
<input type="text"
class="form-control studylist-search"
id="studyDescription"
placeholder="Study Description">
</th>
<th class="numberOfStudyRelatedInstances">
<div id="_numberOfStudyRelatedInstances" class="sortingCell">
<span># Images</span>
<i class="{{sortingColumnsIcons.numberOfStudyRelatedInstances}}">&nbsp;</i>
</div>
<input type="text" class="form-control studylist-search invisible"/>
</th>
</tr>
</thead>
<tbody id="studyListData">
{{#each study in studies}}
{{>studylistStudy (clone study this)}}
{{/each}}
</tbody>
</table>
<!-- Pagination -->
{{> studylistPagination }}
{{#if session "showLoadingText"}}
{{>loadingText}}
{{ else }}
{{#unless numberOfStudies}}
<div class="notFound">No matching results</div>
{{/unless}}
{{/if}}
</div>
</template>
@@ -0,0 +1,290 @@
import { OHIF } from 'meteor/ohif:core';
Session.setDefault('showLoadingText', true);
Template.studylistResult.helpers({
/**
* Returns a sorted instance of the StudyListStudies Collection
* by Patient name and Study Date in Ascending order.
*/
studies() {
const instance = Template.instance();
let studies;
let sortOption = {
patientName: 1,
studyDate: 1
};
// Update sort option if session is defined
if (Session.get('sortOption')) {
sortOption = Session.get('sortOption');
}
// Pagination parameters
const rowsPerPage = instance.rowsPerPage.get();
const currentPage = instance.currentPage.get();
const offset = rowsPerPage * currentPage;
const limit = offset + rowsPerPage;
studies = StudyListStudies.find({}, {
sort: sortOption
}).fetch();
if (!studies) {
return;
}
// Update record count
instance.recordCount.set(studies.length);
// Limit studies
return studies.slice(offset, limit);
},
numberOfStudies() {
return StudyListStudies.find().count();
},
sortingColumnsIcons() {
const instance = Template.instance();
let sortingColumnsIcons = {};
Object.keys(instance.sortingColumns.keys).forEach(key => {
const value = instance.sortingColumns.get(key);
if (value === 1) {
sortingColumnsIcons[key] = 'fa fa-fw fa-sort-up';
} else if (value === -1) {
sortingColumnsIcons[key] = 'fa fa-fw fa-sort-down';
} else {
// fa-fw is blank
sortingColumnsIcons[key] = 'fa fa-fw';
}
});
return sortingColumnsIcons;
}
});
var studyDateFrom;
var studyDateTo;
var filter;
/**
* Transforms an input string into a search filter for
* the StudyList Search call
*
* @param filter The input string to be searched for
* @returns {*}
*/
function getFilter(filter) {
if (filter && filter.length && filter.substr(filter.length - 1) !== '*') {
filter += '*';
}
return filter;
}
/**
* Search for a value in a string
*/
function isIndexOf(mainVal, searchVal) {
if (mainVal === undefined || mainVal === '' || mainVal.indexOf(searchVal) > -1){
return true;
}
return false;
}
/**
* Replace object if undefined
*/
function replaceUndefinedColumnValue(text) {
if (text === undefined || text === 'undefined') {
return '';
} else {
return text.toUpperCase();
}
}
/**
/**
* Convert string to study date
*/
function convertStringToStudyDate(dateStr) {
const y = dateStr.substring(0, 4);
const m = dateStr.substring(4, 6);
const d = dateStr.substring(6, 8);
const newDateStr = m + '/' + d + '/' + y;
return new Date(newDateStr);
}
/**
* Runs a search for studies matching the studylist query parameters
* Inserts the identified studies into the StudyListStudies Collection
*/
function search() {
console.log('search()');
// Show loading message
Session.set('showLoadingText', true);
// Create the filters to be used for the StudyList Search
filter = {
patientName: getFilter($('input#patientName').val()),
patientId: getFilter($('input#patientId').val()),
accessionNumber: getFilter($('input#accessionNumber').val()),
studyDescription: getFilter($('input#studyDescription').val()),
studyDateFrom: studyDateFrom,
studyDateTo: studyDateTo,
modalitiesInStudy: $('input#modality').val() ? $('input#modality').val() : ""
};
// Make sure that modality has a reasonable value, since it is occasionally
// returned as 'undefined'
const modality = replaceUndefinedColumnValue($('input#modality').val());
// Clear all current studies
StudyListStudies.remove({});
Meteor.call('StudyListSearch', filter, (error, studies) => {
console.log('StudyListSearch');
if (error) {
OHIF.log.warn(error);
return;
}
// Hide loading text
Session.set('showLoadingText', false);
if (!studies) {
return;
}
// Loop through all identified studies
studies.forEach(study => {
// Search the rest of the parameters that aren't done via the server call
if (isIndexOf(study.modalities, modality) &&
(new Date(studyDateFrom).setHours(0, 0, 0, 0) <= convertStringToStudyDate(study.studyDate) || !studyDateFrom || studyDateFrom === '') &&
(convertStringToStudyDate(study.studyDate) <= new Date(studyDateTo).setHours(0, 0, 0, 0) || !studyDateTo || studyDateTo === '')) {
// Convert numberOfStudyRelatedInstance string into integer
study.numberOfStudyRelatedInstances = !isNaN(study.numberOfStudyRelatedInstances) ? parseInt(study.numberOfStudyRelatedInstances) : undefined;
// Insert any matching studies into the StudyListStudies Collection
StudyListStudies.insert(study);
}
});
});
}
Template.studylistResult.onCreated(() => {
let instance = Template.instance();
instance.sortOption = new ReactiveVar();
instance.sortingColumns = new ReactiveDict();
// Pagination parameters
// Rows per page is 25 as default
instance.rowsPerPage = new ReactiveVar(25);
// Set currentPage indexed 0
instance.currentPage = new ReactiveVar(0);
instance.recordCount = new ReactiveVar();
// Set sortOption
const sortOptionSession = Session.get('sortOption');
if (sortOptionSession) {
instance.sortingColumns.set(sortOptionSession);
} else {
instance.sortingColumns.set({
patientName: 1,
studyDate: 1,
patientId: 0,
accessionNumber: 0,
studyDescription: 0,
modality: 0,
numberOfStudyRelatedInstances: 0
});
}
});
Template.studylistResult.onRendered(() => {
const instance = Template.instance();
// Initialize daterangepicker
const today = moment();
const lastWeek = moment().subtract(6, 'days');
const lastMonth = moment().subtract(29, 'days');
const datePicker = instance.$('#studyDate').daterangepicker({
maxDate: today,
autoUpdateInput: true,
ranges: {
Today: [today, today],
'Last 7 Days': [lastWeek, today],
'Last 30 Days': [lastMonth, today]
}
}).data('daterangepicker');
// Retrieve all studies
search();
});
function resetSortingColumns(instance, sortingColumn) {
Object.keys(instance.sortingColumns.keys).forEach(key => {
if (key !== sortingColumn) {
instance.sortingColumns.set(key, null);
}
});
}
Template.studylistResult.events({
'keydown input'(event) {
if (event.which === 13) { // Enter
search();
}
},
'onsearch input'() {
search();
},
'change #studyDate'(event) {
let dateRange = $(event.currentTarget).val();
// Remove all space chars
dateRange = dateRange.replace(/ /g, '');
// Split dateRange into subdates
const dates = dateRange.split('-');
studyDateFrom = dates[0];
studyDateTo = dates[1];
if (dateRange !== '') {
search();
}
},
'click div.sortingCell'(event, instance) {
const elementId = event.currentTarget.id;
// Remove _ from id
const columnName = elementId.replace('_', '');
let sortOption = {};
resetSortingColumns(instance, columnName);
const columnObject = instance.sortingColumns.get(columnName);
if (columnObject) {
instance.sortingColumns.set(columnName, columnObject * -1);
sortOption[columnName] = columnObject * -1;
} else {
instance.sortingColumns.set(columnName, 1);
sortOption[columnName] = 1;
}
instance.sortOption.set(sortOption);
Session.set('sortOption', sortOption);
}
});
@@ -0,0 +1,241 @@
@import "{design}/app"
$tableHoverColor = #2c363f
$tableTextPrimaryColor = white
$tableTextSecondaryColor = #91b9cd
$inputBackgroundColor = #2c363f
$inputPlaceholderColor = lightgray
$studyListToolbarHeight = 75px
$tablePadding = 8%
$bodyCellHeight = 40px
.studyListToolbar
theme('background', '$uiGrayDarker')
height: $studyListToolbarHeight
border-bottom: 2px solid black
padding: 0 30px
div
display: inline-block
.header
font-size: 22px
font-weight: 300
color: $tableTextSecondaryColor
padding: 0 10px
line-height: $studyListToolbarHeight
.studylistToolbar
height: $studyListToolbarHeight
line-height: $studyListToolbarHeight
.studyCount
theme('color', '$uiSkyBlue')
font-size: 40px
font-weight: 100
padding: 0 10px
line-height: $studyListToolbarHeight
.theadBackground
theme('background-color', '$uiGrayDarker')
width: 100%
height: 122px
position: absolute
z-index: 1
#studyListContainer
background: black
width: 100%
padding: 0 $tablePadding
position: absolute
z-index: 2
.loadingTextDiv
color: $tableTextSecondaryColor
font-size: 30px
.notFound
color: $tableTextSecondaryColor
font-size: 30px
font-weight: 200
text-align: center
table#tblStudyList
thead
theme('background-color', '$uiGrayDarker')
white-space: nowrap
tr
th
padding: 0
border-bottom: solid 1px #6fbde2
width: 100%
&:first-child
padding-left: $tablePadding
&:last-child
padding-right: $tablePadding
div.sortingCell
display: inline-block
cursor: pointer
width: 100%
min-width: 95px
margin: 0 auto
color: $tableTextPrimaryColor
font-weight: 400
padding: 20px
span
font-size: 15px
float: left
i
margin: 0 5px
&:hover
theme('color', '$activeColor')
&.active, &:active
theme('color', '$activeColor')
input.studylist-search
height: 34px
margin: 0 5px 20px 5px
padding: 20px
cursor: pointer
border: none
background-color: $inputBackgroundColor
color: $inputPlaceholderColor
font-size: 10pt
font-weight: normal
width: calc(100% - 10px)
border-radius: 4px
box-sizing: border-box
transition(all 0.15s ease)
placeholder-color($inputPlaceholderColor)
&.invisible
visibility: hidden
&:active, &:hover
background-color: $inputBackgroundColor
::-webkit-datetime-edit-year-field:not([aria-valuenow]),
::-webkit-datetime-edit-month-field:not([aria-valuenow]),
::-webkit-datetime-edit-day-field:not([aria-valuenow])
color: transparent
tbody
tr
padding: 5px
background-color: black
&:nth-child(even)
theme('background-color', '$uiGrayDarker')
td
height: $bodyCellHeight
line-height: $bodyCellHeight
color: $tableTextPrimaryColor
font-weight: 300
border-top: 1px solid #436270
white-space: nowrap
transition(all 0.1s ease)
&:first-child
padding-left: $tablePadding
&:last-child
padding-right: $tablePadding
&:hover, &:active, &.active
background-color: $tableHoverColor
color: white
td
// This selector is necessary to override bootstrap's 'table' class
border-top: 1px solid #436270
border-bottom: 1px solid #436270
background-color: $tableHoverColor
@media only screen and (max-width: 1362px)
$tablePadding = 5%
#studyListContainer
padding: 0 $tablePadding
table#tblStudyList
thead,
tbody
tr
th,
td
&:first-child
padding-left: $tablePadding
&:last-child
padding-right: $tablePadding
@media only screen and (max-width: 1161px)
$tablePadding = 3%
#studyListContainer
padding: 0 $tablePadding
table#tblStudyList
thead,
tbody
tr
th,
td
&:first-child
padding-left: $tablePadding
&:last-child
padding-right: $tablePadding
@media only screen and (max-width: 1069px)
$tablePaddingMediumScreen = 5px
.studyListToolbar
padding: 0 $tablePaddingMediumScreen
#studyListContainer
padding: 0
table#tblStudyList
thead > tr > th
&:first-child
padding-left: $tablePaddingMediumScreen
&:last-child
padding-right: $tablePaddingMediumScreen
input.worklist-search
padding: 10px
div.sortingCell
padding: 10px 5px
min-width: auto
i
width: auto
tbody > tr > td
&:first-child
padding-left: $tablePaddingMediumScreen
&:last-child
padding-right: $tablePaddingMediumScreen
.worklistPagination
.row
margin-left: 0
margin-right: 0
@@ -0,0 +1,25 @@
<template name="studylistStudy">
<tr class="studylistStudy noselect">
<td class="patientName">
{{formatPN patientName}}
</td>
<td class="patientId">
{{patientId}}
</td>
<td class="accessionNumber">
{{accessionNumber}}
</td>
<td class="studyDate">
{{formatDA studyDate}}
</td>
<td class="modalities">
{{modalities}}
</td>
<td class="studyDescription">
{{studyDescription}}
</td>
<td class="numberOfStudyRelatedInstances">
{{numberOfStudyRelatedInstances}}
</td>
</tr>
</template>
@@ -0,0 +1,168 @@
import { OHIF } from 'meteor/ohif:core';
// Maybe we should use regular StudyList collection?
StudyListSelectedStudies = new Meteor.Collection(null);
StudyListSelectedStudies._debugName = 'StudyListSelectedStudies';
function handleShiftClick(studyRow, data) {
//OHIF.log.info('shiftKey');
var studyInstanceUid = studyRow.attr('studyInstanceUid');
// Select all rows in between these two rows
if (StudyList.previouslySelected) {
var previous = $(StudyList.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
StudyListSelectedStudies.remove({});
StudyList.previouslySelected = undefined;
return;
}
// Loop through the rows in between current and previous selected studies
rowsInBetween.each(function() {
var row = $(this);
if (row.hasClass('active')) {
// If we find one that is already selected, do nothing
return;
}
// Get the relevant studyInstanceUid
var studyInstanceUid = row.attr('studyInstanceUid');
// Retrieve the data context through Blaze
var data = Blaze.getData(this);
// Set the current study as selected
StudyListSelectedStudies.insert(data);
row.addClass('active');
// When we reach the currently clicked-on row, stop the loop
return !row.is(studyRow);
});
} else {
// Set the current study as selected
StudyListSelectedStudies.insert(data);
studyRow.addClass('active');
}
}
function handleCtrlClick(studyRow, data) {
//OHIF.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
StudyListSelectedStudies.remove({
studyInstanceUid: data.studyInstanceUid
});
} else {
// Set the current study as selected
StudyListSelectedStudies.insert(data);
studyRow.addClass('active');
// Set this as the previously selected row, so the user can
// use Shift to select from this point onwards
StudyList.previouslySelected = studyRow;
OHIF.log.info('StudyList PreviouslySelected set: ' + studyRow.index());
}
}
Template.studylistStudy.onRendered(function() {
var instance = this;
var elem = instance.$('tr.studylistStudy').get(0);
// Enable HammerJS to allow touch support
var mc = new Hammer.Manager(elem);
var doubleTapRecognizer = new Hammer.Tap({
event: 'doubletap',
taps: 2,
interval: 500,
threshold: 30,
posThreshold: 30
});
mc.add(doubleTapRecognizer);
});
Template.studylistStudy.events({
'click tr.studylistStudy': 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
//OHIF.log.info('Regular click');
// Clear all selected studies
StudyListSelectedStudies.remove({});
$('tr.studylistStudy').removeClass('active');
// Set the previous study to the currently clicked-on study
StudyList.previouslySelected = studyRow;
//OHIF.log.info('StudyList PreviouslySelected set: ' + studyRow.index());
// Set the current study as selected
StudyListSelectedStudies.insert(data);
studyRow.addClass('active');
}
},
'mousedown tr.studylistStudy': function(e) {
// This event handler is meant to handle middle-click on a study
if (e.which !== 2) {
return;
}
var data = this;
var middleClickOnStudy = StudyList.callbacks.middleClickOnStudy;
if (middleClickOnStudy && typeof middleClickOnStudy === 'function') {
middleClickOnStudy(data);
}
},
'dblclick tr.studylistStudy, doubletap tr.studylistStudy': function(e) {
if (e.which !== undefined && e.which !== 1) {
return;
}
var data = this;
var dblClickOnStudy = StudyList.callbacks.dblClickOnStudy;
if (dblClickOnStudy && typeof dblClickOnStudy === 'function') {
dblClickOnStudy(data);
}
},
'contextmenu tr.studylistStudy, press tr.studylistStudy': function(e, template) {
$(e.currentTarget).addClass('active');
if (openStudyContextMenu && typeof openStudyContextMenu === 'function') {
e.preventDefault();
openStudyContextMenu(e, template);
return false;
}
}
});
@@ -0,0 +1,2 @@
.studylistStudy
cursor: pointer
@@ -0,0 +1,12 @@
<template name="studylistToolbar">
<div class='studylistToolbar'>
{{#if importSupported }}
<div class="addNewStudy btn-file">
<label for="btnImport">
<i class="fa fa-plus"></i> Add new study
</label>
<input id="btnImport" class='js-import-files' type="file" webkitdirectory directory multiple>
</div>
{{/if }}
</div>
</template>
@@ -0,0 +1,36 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { $ } from 'meteor/jquery';
Template.studylistToolbar.onCreated(() => {
Meteor.call('importSupported', (error, result) => {
if (error || !result) {
Session.set('importSupported', false);
} else {
Session.set('importSupported', true);
}
});
});
Template.studylistToolbar.events({
'change .js-import-files'(event) {
// Get selected files located in the client machine
const selectedFiles = $.map(event.currentTarget.files, value => value);
importStudies(selectedFiles);
},
'click .js-import-files'(event) {
// Reset file input
$(event.currentTarget).val('');
}
});
Template.studylistToolbar.helpers({
importSupported() {
const importSupported = Session.get('importSupported');
return (importSupported && OHIF.uiSettings.studyListFunctionsEnabled);
}
});
@@ -0,0 +1,27 @@
@import "{design}/app"
.studylistToolbar
.addNewStudy
margin: 0 10px
label
theme('color', '$textSecondaryColor')
font-weight: 400
cursor: pointer
*
pointer-events: none
&:hover
theme('color', '$hoverColor')
&:active
theme('color', '$activeColor')
input
width: 0.1px;
height: 0.1px
opacity: 0
overflow: hidden
position: absolute
z-index: -1