PWV-1: Refactoring ohif:studylist package
This commit is contained in:
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%
|
||||
+29
@@ -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>
|
||||
+58
@@ -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));
|
||||
}
|
||||
});
|
||||
+43
@@ -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
|
||||
|
||||
+107
@@ -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}}"> </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}}"> </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}}"> </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}}"> </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}}"> </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}}"> </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}}"> </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>
|
||||
+290
@@ -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);
|
||||
}
|
||||
});
|
||||
+241
@@ -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
|
||||
+25
@@ -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>
|
||||
+168
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
.studylistStudy
|
||||
cursor: pointer
|
||||
+12
@@ -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>
|
||||
+36
@@ -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);
|
||||
}
|
||||
});
|
||||
+27
@@ -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
|
||||
Reference in new issue
Block a user