PWV-1: Refactoring ohif:studylist package

This commit is contained in:
Bruno Alves de Faria 2016-09-26 08:34:26 -03:00 committed by Erik Ziegler
parent 984ef94403
commit 4d79bf84a8
79 changed files with 450 additions and 551 deletions

View File

@ -35,7 +35,6 @@
{{ >serverInformationModal }} {{ >serverInformationModal }}
{{ >confirmRemoveTimepointAssociation }} {{ >confirmRemoveTimepointAssociation }}
{{ >lastLoginModal }} {{ >lastLoginModal }}
{{ >studyContextMenu }}
{{ >progressDialog }} {{ >progressDialog }}
{{ >viewSeriesDetailsModal }} {{ >viewSeriesDetailsModal }}
</template> </template>

View File

@ -33,7 +33,6 @@
</div> </div>
{{ >serverInformationModal }} {{ >serverInformationModal }}
{{ >lastLoginModal }} {{ >lastLoginModal }}
{{ >studyContextMenu }}
{{ >progressDialog }} {{ >progressDialog }}
{{ >viewSeriesDetailsModal }} {{ >viewSeriesDetailsModal }}
</template> </template>

View File

@ -12,7 +12,7 @@ OHIF.mixins.button = new OHIF.Mixin({
const component = instance.component; const component = instance.component;
// Set the element to be controlled // Set the element to be controlled
component.$element = instance.$('button:first'); component.$element = instance.$('button').first();
} }
} }
}); });

View File

@ -54,7 +54,7 @@ OHIF.mixins.form = new OHIF.Mixin({
const component = instance.component; const component = instance.component;
// Set the component main and style elements // Set the component main and style elements
component.$style = component.$element = instance.$('form:first'); component.$style = component.$element = instance.$('form').first();
// Block page redirecting on submit // Block page redirecting on submit
component.$element[0].onsubmit = () => false; component.$element[0].onsubmit = () => false;
@ -66,7 +66,7 @@ OHIF.mixins.form = new OHIF.Mixin({
const targetKey = $(event.currentTarget).attr('data-target'); const targetKey = $(event.currentTarget).attr('data-target');
// Focus the first input inside the element with error state // Focus the first input inside the element with error state
instance.$(`.state-error[data-key="${targetKey}"] :input:first`).focus(); instance.$(`.state-error[data-key="${targetKey}"]`).find(':input:first').focus();
} }
}, },

View File

@ -147,7 +147,7 @@ OHIF.mixins.group = new OHIF.Mixin({
const component = instance.component; const component = instance.component;
// Set the element to be controlled // Set the element to be controlled
component.$element = instance.$('.component-group:first'); component.$element = instance.$('.component-group').first();
} }
} }
}); });

View File

@ -12,7 +12,7 @@ OHIF.mixins.input = new OHIF.Mixin({
const component = instance.component; const component = instance.component;
// Set the element to be controlled // Set the element to be controlled
component.$element = instance.$('input:first'); component.$element = instance.$('input').first();
} }
} }
}); });

View File

@ -12,7 +12,7 @@ OHIF.mixins.link = new OHIF.Mixin({
const component = instance.component; const component = instance.component;
// Set the element to be controlled // Set the element to be controlled
component.$element = instance.$('a:first'); component.$element = instance.$('a').first();
}, },
events: { events: {

View File

@ -12,7 +12,7 @@ OHIF.mixins.select = new OHIF.Mixin({
const component = instance.component; const component = instance.component;
// Set the element to be controlled // Set the element to be controlled
component.$element = instance.$('select:first'); component.$element = instance.$('select').first();
} }
} }
}); });

View File

@ -0,0 +1,3 @@
<template name="dropdownBackdrop">
<div class="dropdown-backdrop"></div>
</template>

View File

@ -0,0 +1,8 @@
<template name="dropdownForm">
{{#form class='dropdown' api=this.api hideValidationBox=true}}
{{>dropdownBackdrop}}
<ul class="dropdown-menu">
{{>UI.contentBlock}}
</ul>
{{/form}}
</template>

View File

@ -0,0 +1,25 @@
import { Template } from 'meteor/templating';
import { Blaze } from 'meteor/blaze';
import { _ } from 'meteor/underscore';
Template.dropdownForm.onRendered(() => {
const instance = Template.instance();
// Show the dropdown
instance.$('.dropdown').addClass('open');
// Destroy the Blaze created view (either created with template calls or with renderWithData)
instance.destroyView = () => {
if (_.isFunction(instance.data.destroyView)) {
instance.data.destroyView();
} else {
Blaze.remove(instance.view);
}
};
});
Template.dropdownForm.events({
'click .dropdown'(event, instance) {
instance.destroyView();
}
});

View File

@ -3,6 +3,10 @@ import './dialog/form.js';
import './dialog/login.html'; import './dialog/login.html';
import './dialog/login.js'; import './dialog/login.js';
import './dropdown/backdrop.html';
import './dropdown/form.html';
import './dropdown/form.js';
import './form/button.html'; import './form/button.html';
import './form/form.html'; import './form/form.html';
import './form/group.html'; import './form/group.html';

View File

@ -36,10 +36,15 @@ class Bounded {
options(options={}) { options(options={}) {
// Process the given options and store it in the instance // Process the given options and store it in the instance
const { boundingElement, allowResizing } = options; const { boundingElement, allowResizing } = options;
this.boundingElement = boundingElement || window; this.boundingElement = boundingElement;
this.$boundingElement = $(this.boundingElement); this.$boundingElement = $(this.boundingElement);
this.allowResizing = allowResizing; this.allowResizing = allowResizing;
// Check for fixed positioning
if (this.$element.css('position') === 'fixed') {
this.boundingElement = window;
}
// Destroy and initialize again the instance // Destroy and initialize again the instance
this.destroy(); this.destroy();
this.init(); this.init();
@ -55,6 +60,9 @@ class Bounded {
// Add the bounded class to the element // Add the bounded class to the element
this.$element.addClass('bounded'); this.$element.addClass('bounded');
// Trigger the bounding check for the first timepoint
setTimeout(() => this.$element.trigger('spatialChanged'));
} }
// Destroy this instance, returning the element to its previous state // Destroy this instance, returning the element to its previous state
@ -70,12 +78,26 @@ class Bounded {
// Create the result object // Create the result object
const result = {}; const result = {};
// Check if the element is the window
if (element === window) {
const $window = $(window);
const width = $window.outerWidth();
const height = $window.outerHeight();
return {
width,
height,
x0: 0,
y0: 0,
x1: width,
y1: height
};
}
// Get the jQuery object for the element // Get the jQuery object for the element
const el = element === window ? window.document.body : element; const $element = $(element);
const $element = $(el);
// Get the element style // Get the element style
const style = el.style; const style = element.style;
// Get the integer numbers for element's width // Get the integer numbers for element's width
if (style.width && style.width.indexOf('px') > -1) { if (style.width && style.width.indexOf('px') > -1) {
@ -91,10 +113,13 @@ class Bounded {
result.height = $element.outerHeight(); result.height = $element.outerHeight();
} }
// Get the position property based on the element position CSS attribute
const positionProperty = $element.css('position') === 'fixed' ? 'position' : 'offset';
// Get the element's start position // Get the element's start position
const offset = $element.offset(); const position = $element[positionProperty]();
result.x0 = offset.left; result.x0 = position.left;
result.y0 = offset.top; result.y0 = position.top;
// Get the element's end position // Get the element's end position
result.x1 = result.x0 + result.width; result.x1 = result.x0 + result.width;

View File

@ -0,0 +1,29 @@
import { Template } from 'meteor/templating';
import { Blaze } from 'meteor/blaze';
import { _ } from 'meteor/underscore';
import { OHIF } from 'meteor/ohif:core';
OHIF.ui.showFormDropdown = (templateName, dropdownData) => {
// Check if the given template exists
const template = Template[templateName];
if (!template) {
throw {
name: 'TEMPLATE_NOT_FOUND',
message: `Template ${templateName} not found.`
};
}
// Prepare the method to destroy the view
let view;
const destroyView = () => Blaze.remove(view);
const templateData = _.extend({}, dropdownData, {
destroyView
});
// Render the dialog with the given template and data
view = Blaze.renderWithData(template, templateData, document.body);
// Return the dropdown element to enable position manipulation
const dropdown = view.firstNode();
return dropdown;
};

View File

@ -1,4 +1,5 @@
import './bounded/bounded.js'; import './bounded/bounded.js';
import './dialog/form.js'; import './dialog/form.js';
import './draggable/draggable.js'; import './draggable/draggable.js';
import './dropdown/form.js';
import './resizable/resizable.js'; import './resizable/resizable.js';

View File

@ -7,8 +7,7 @@ import { Meteor } from 'meteor/meteor';
const OHIF = { const OHIF = {
log: {}, log: {},
ui: {}, ui: {},
viewer: {}, viewer: {}
measurements: {}
}; };
// Expose the OHIF object to the client if it is on development mode // Expose the OHIF object to the client if it is on development mode

View File

@ -0,0 +1,3 @@
import { OHIF } from 'meteor/ohif:core';
OHIF.measurements = {};

View File

@ -1,2 +1,3 @@
import './base.js';
import './configuration'; import './configuration';
import './schema'; import './schema';

View File

@ -1,3 +1,7 @@
import { Template } from 'meteor/templating';
import { moment } from 'meteor/momentjs:moment';
import { OHIF } from 'meteor/ohif:core';
/** /**
* Finds related studies within defined time window of =/- 14 days of selected studies * Finds related studies within defined time window of =/- 14 days of selected studies
* @param selectedStudies * @param selectedStudies
@ -14,13 +18,13 @@ function getDateRange(selectedStudies, range) {
return; return;
} }
var earliestStudy = selectedStudies[0]; const earliestStudy = selectedStudies[0];
var latestStudy = selectedStudies[selectedStudies.length - 1]; const latestStudy = selectedStudies[selectedStudies.length - 1];
var earliestDate = moment(earliestStudy.studyDate, 'YYYYMMDD'); const earliestDate = moment(earliestStudy.studyDate, 'YYYYMMDD');
earliestDate.subtract(range); earliestDate.subtract(range);
var latestDate = moment(latestStudy.studyDate, 'YYYYMMDD'); const latestDate = moment(latestStudy.studyDate, 'YYYYMMDD');
latestDate.add(range); latestDate.add(range);
return { return {
@ -43,12 +47,12 @@ function autoSelectStudies(selectedStudies) {
return; return;
} }
var range = getDateRange(selectedStudies); const range = getDateRange(selectedStudies);
// Fetch autoselected studies based on the date range // Fetch autoselected studies based on the date range
// Note that we used MongoDB's fetch here so we have a mutable array, // Note that we used MongoDB's fetch here so we have a mutable array,
// rather than a Cursor // rather than a Cursor
var autoselected = StudyListStudies.find({ const autoselected = StudyListStudies.find({
studyDate: { studyDate: {
$gte: range.earliestDate.format('YYYYMMDD'), $gte: range.earliestDate.format('YYYYMMDD'),
$lte: range.latestDate.format('YYYYMMDD') $lte: range.latestDate.format('YYYYMMDD')
@ -60,12 +64,10 @@ function autoSelectStudies(selectedStudies) {
}).fetch(); }).fetch();
// Make an array of studyInstanceUids in selectedStudies // Make an array of studyInstanceUids in selectedStudies
var studyInstanceUids = selectedStudies.map(function(selectedStudy) { const studyInstanceUids = selectedStudies.map(selectedStudy => selectedStudy.studyInstanceUid);
return selectedStudy.studyInstanceUid;
});
autoselected.forEach(function(study) { autoselected.forEach(study => {
var exists = studyInstanceUids.indexOf(study.studyInstanceUid); const exists = studyInstanceUids.indexOf(study.studyInstanceUid);
if (exists > -1) { if (exists > -1) {
study.autoselected = false; study.autoselected = false;
return; return;
@ -84,12 +86,8 @@ Template.studyAssociationTable.helpers({
* *
* @returns {Array.<T>} * @returns {Array.<T>}
*/ */
relevantStudies: function() { relevantStudies() {
var selectedStudies = StudyListSelectedStudies.find({}, { const selectedStudies = OHIF.studylist.getSelectedStudies();
sort: {
studyDate: 1
}
}).fetch() || [];
return autoSelectStudies(selectedStudies); return autoSelectStudies(selectedStudies);
}, },
@ -98,7 +96,7 @@ Template.studyAssociationTable.helpers({
* *
* @returns {Array.<T>} * @returns {Array.<T>}
*/ */
timepointOptions: function() { timepointOptions() {
return [{ return [{
value: 'baseline', value: 'baseline',
name: 'Baseline', name: 'Baseline',
@ -109,26 +107,18 @@ Template.studyAssociationTable.helpers({
checked: false checked: false
}]; }];
}, },
earliestDate: function() { earliestDate() {
var selectedStudies = StudyListSelectedStudies.find({}, { const selectedStudies = OHIF.studylist.getSelectedStudies();
sort: {
studyDate: 1
}
}).fetch();
var range = getDateRange(selectedStudies); const range = getDateRange(selectedStudies);
if (range) { if (range) {
return range.earliestDate; return range.earliestDate;
} }
}, },
latestDate: function() { latestDate() {
var selectedStudies = StudyListSelectedStudies.find({}, { const selectedStudies = OHIF.studylist.getSelectedStudies();
sort: {
studyDate: 1
}
}).fetch();
var range = getDateRange(selectedStudies); const range = getDateRange(selectedStudies);
if (range) { if (range) {
return range.latestDate; return range.latestDate;
} }
@ -136,9 +126,9 @@ Template.studyAssociationTable.helpers({
}); });
Template.studyAssociationTable.events({ Template.studyAssociationTable.events({
'change input.includeStudy': function(e) { 'change input.includeStudy'(event, instance) {
var checkbox = e.currentTarget; const checkbox = event.currentTarget;
var studyDataCells = $(checkbox).parents('tr').find('td.studyDataCell'); const studyDataCells = $(checkbox).parents('tr').find('td.studyDataCell');
if (checkbox.checked === true) { if (checkbox.checked === true) {
studyDataCells.removeClass('disabled'); studyDataCells.removeClass('disabled');
studyDataCells.find('input').attr('disabled', false); studyDataCells.find('input').attr('disabled', false);

View File

@ -1,30 +1,20 @@
<template name="longitudinalStudyListContextMenu"> <template name="longitudinalStudyListContextMenu">
{{#if uiSettings.studyListFunctionsEnabled}} {{#dropdownForm}}
<div id="studyContextMenu" class="studyContextMenu noselect" <li><a id="viewStudies" type="button" title="View Studies">View</a></li>
oncontextmenu='return false;' <li class="divider" role="separator"></li>
unselectable='on' <!-- {{#link action='associate'}}Associate{{/link}} -->
onselectstart='return false;'> <li><a id="launchStudyAssociation" title="Launch Study Association">Associate</a></li>
<ul> <li><a id="launchRemoveAssociation" type="button" data-toggle="modal"
<li> data-target="#confirmRemoveTimepointAssociation" title="Remove Timepoint Association"
<a id="viewStudies" type="button" title="View Studies">View</a> >Remove Association</a></li>
<hr/> <li class="divider" role="separator"></li>
<!-- {{#link action='associate'}}Associate{{/link}} --> <li><a id="viewSeriesDetails" type="button"
<a id="launchStudyAssociation" title="Launch Study Association">Associate</a> title="View Series Details"
<a id="launchRemoveAssociation" type="button" >View Series Details</a></li>
data-toggle="modal" <li class="disabled"><a class="disabled">Anonymize</a></li>
data-target="#confirmRemoveTimepointAssociation" <li class="disabled"><a class="disabled">Send</a></li>
title="Remove Timepoint Association">Remove Association</a> <li class="divider" role="separator"></li>
<hr/> <li><a id="exportSelectedStudies" title="Export Selected Studies">Export</a></li>
<a id="viewSeriesDetails" type="button" <li class="disabled"><a class="disabled">Delete</a></li>
title="View Series Details">View Series Details</a> {{/dropdownForm}}
<a class="disabled">Anonymize</a>
<a class="disabled">Send</a>
<hr/>
<a id="exportSelectedStudies"
title="Export Selected Studies">Export</a>
<a class="disabled">Delete</a>
</li>
</ul>
</div>
{{/if}}
</template> </template>

View File

@ -1,3 +1,6 @@
import { Template } from 'meteor/templating';
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { OHIF } from 'meteor/ohif:core'; import { OHIF } from 'meteor/ohif:core';
// Use Aldeed's meteor-template-extension package to replace the // Use Aldeed's meteor-template-extension package to replace the
@ -7,21 +10,17 @@ const defaultTemplate = 'studyContextMenu';
Template.longitudinalStudyListContextMenu.replaces(defaultTemplate); Template.longitudinalStudyListContextMenu.replaces(defaultTemplate);
StudyList.functions['launchStudyAssociation'] = () => OHIF.ui.showFormDialog('dialogStudyAssociation'); StudyList.functions.launchStudyAssociation = () => OHIF.ui.showFormDialog('dialogStudyAssociation');
StudyList.functions['removeTimepointAssociations'] = removeTimepointAssociations; StudyList.functions.removeTimepointAssociations = removeTimepointAssociations;
StudyList.functions['exportSelectedStudies'] = exportSelectedStudies; StudyList.functions.exportSelectedStudies = exportSelectedStudies;
StudyList.functions['viewStudies'] = viewStudies; StudyList.functions.viewStudies = viewStudies;
/** /**
* Removes all present study / timepoint associations from the Clinical Trial * Removes all present study / timepoint associations from the Clinical Trial
*/ */
function removeTimepointAssociations() { function removeTimepointAssociations() {
// Get a Cursor pointing to the selected Studies from the StudyList // Get a Cursor pointing to the selected Studies from the StudyList
const selectedStudies = StudyListSelectedStudies.find({}, { const selectedStudies = OHIF.studylist.getSelectedStudies();
sort: {
studyDate: 1
}
});
// Loop through the Cursor of Selected Studies // Loop through the Cursor of Selected Studies
selectedStudies.forEach(function(selectedStudy) { selectedStudies.forEach(function(selectedStudy) {
@ -31,14 +30,14 @@ function removeTimepointAssociations() {
return; return;
} }
let timepoint = timepointApi.study(data.study.studyInstanceUid)[0]; let timepoint = timepointApi.study(selectedStudy.studyInstanceUid)[0];
if (!timepoint) { if (!timepoint) {
return; return;
} }
// Find the index of the current studyInstanceUid in the array // Find the index of the current studyInstanceUid in the array
// of reference studyInstanceUids // of reference studyInstanceUids
var index = timepoint.studyInstanceUids.indexOf(study.studyInstanceUid); const index = timepoint.studyInstanceUids.indexOf(selectedStudy.studyInstanceUid);
if (index < 0) { if (index < 0) {
return; return;
} }
@ -57,14 +56,14 @@ function removeTimepointAssociations() {
}); });
// Log // Log
var hipaaEvent = { const hipaaEvent = {
eventType: 'modify', eventType: 'modify',
userId: Meteor.userId(), userId: Meteor.userId(),
userName: Meteor.user().profile.fullName, userName: Meteor.user().profile.fullName,
collectionName: "Timepoints", collectionName: 'Timepoints',
recordId: study.timepointId, recordId: selectedStudy.timepointId,
patientId: study.patientId, patientId: selectedStudy.patientId,
patientName: study.patientName patientName: selectedStudy.patientName
}; };
HipaaLogger.logEvent(hipaaEvent); HipaaLogger.logEvent(hipaaEvent);
} else { } else {
@ -77,14 +76,14 @@ function removeTimepointAssociations() {
} }
// Log // Log
var hipaaEvent = { const hipaaEvent = {
eventType: 'delete', eventType: 'delete',
userId: Meteor.userId(), userId: Meteor.userId(),
userName: Meteor.user().profile.fullName, userName: Meteor.user().profile.fullName,
collectionName: "Timepoints", collectionName: 'Timepoints',
recordId: study.timepointId, recordId: selectedStudy.timepointId,
patientId: study.patientId, patientId: selectedStudy.patientId,
patientName: study.patientName patientName: selectedStudy.patientName
}; };
HipaaLogger.logEvent(hipaaEvent); HipaaLogger.logEvent(hipaaEvent);
}); });
@ -94,43 +93,32 @@ function removeTimepointAssociations() {
// ---------- TODO: Remove these duplicated functions below ------------- // ---------- TODO: Remove these duplicated functions below -------------
/** /**
* Exports all selected studies on the studylist * Exports all selected studies on the studylist
*/ */
function exportSelectedStudies() { function exportSelectedStudies() {
const selectedStudies = StudyListSelectedStudies.find({}, { const selectedStudies = OHIF.studylist.getSelectedStudies();
sort: {
studyDate: 1
}
}).fetch();
if (!selectedStudies || !selectedStudies.length) { if (!selectedStudies || !selectedStudies.length) {
return; return;
} }
exportStudies(selectedStudies); OHIF.studylist.exportStudies(selectedStudies);
} }
/** /**
* Loads multiple unassociated studies in the Viewer * Loads multiple unassociated studies in the Viewer
*/ */
function viewStudies() { function viewStudies() {
console.log('viewStudies'); OHIF.log.info('viewStudies');
const selectedStudies = StudyListSelectedStudies.find({}, { const selectedStudies = OHIF.studylist.getSelectedStudies();
sort: {
studyDate: 1
}
}).fetch();
if (!selectedStudies || !selectedStudies.length) { if (!selectedStudies || !selectedStudies.length) {
return; return;
} }
var title = selectedStudies[0].patientName; const title = selectedStudies[0].patientName;
var studyInstanceUids = selectedStudies.map(function(study) { const studyInstanceUids = selectedStudies.map(study => study.studyInstanceUid);
return study.studyInstanceUid;
});
// Generate a unique ID to represent this tab // Generate a unique ID to represent this tab
// We can't just use the Mongo entry ID because // We can't just use the Mongo entry ID because

View File

@ -1,3 +1,3 @@
import './components';
import './lib'; import './lib';
import './helpers'; import './helpers';
import './components';

View File

@ -0,0 +1,6 @@
import { OHIF } from 'meteor/ohif:core';
OHIF.studylist = {
collections: {},
actions: {}
};

View File

@ -1,8 +1,10 @@
import { Mongo } from 'meteor/mongo'; import { Mongo } from 'meteor/mongo';
import { OHIF } from 'meteor/ohif:core';
import { Servers as ServerSchema } from 'meteor/ohif:study-list/both/schema/servers.js'; import { Servers as ServerSchema } from 'meteor/ohif:study-list/both/schema/servers.js';
StudyImportStatus = new Mongo.Collection('studyImportStatus'); const StudyImportStatus = new Mongo.Collection('studyImportStatus');
StudyImportStatus._debugName = 'StudyImportStatus'; StudyImportStatus._debugName = 'StudyImportStatus';
OHIF.studylist.collections.StudyImportStatus = StudyImportStatus;
// Servers describe the DICOM servers configurations // Servers describe the DICOM servers configurations
Servers = new Mongo.Collection('servers'); Servers = new Mongo.Collection('servers');

View File

@ -0,0 +1,4 @@
import './lib';
import './schema';
import './base.js';
import './collections.js';

View File

@ -0,0 +1 @@
import './getCurrentServer.js';

View File

@ -0,0 +1 @@
import './servers.js';

View File

@ -0,0 +1 @@
import './subscriptions.js';

View File

@ -0,0 +1,5 @@
import './progressDialog';
import './seriesDetailsModal';
import './serverInformation';
import './studyContextMenu';
import './studylist';

View File

@ -0,0 +1,3 @@
import './progressDialog.html';
import './progressDialog.styl';
import './progressDialog.js';

View File

@ -6,14 +6,13 @@
<div class="dialogContent"> <div class="dialogContent">
<div class="progress"> <div class="progress">
<div class="progress-bar progress-bar-striped active" <div class="progress-bar progress-bar-striped active"
role="progressbar" role="progressbar"
style="width:{{progressStatus}}"> style="width: {{progressStatus}}%"
{{formatNumberPrecision progressStatus 0}}% >{{formatNumberPrecision progressStatus 0}}%</div>
</div>
</div> </div>
<div id="message"> <div id="message">
{{{progressMessage}}} {{{progressMessage}}}
</div> </div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,4 +1,28 @@
progressDialog = { import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { _ } from 'meteor/underscore';
import { OHIF } from 'meteor/ohif:core';
const updateProgress = numberOfCompleted => {
const progressDialogSettings = Session.get('progressDialogSettings');
progressDialogSettings.numberOfCompleted = numberOfCompleted;
Session.set('progressDialogSettings', progressDialogSettings);
if (progressDialogSettings.numberOfCompleted === progressDialogSettings.numberOfTotal) {
OHIF.studylist.progressDialog.close();
}
};
const setProgressMessage = message => {
let progressDialogSettings = Session.get('progressDialogSettings');
progressDialogSettings.message = message;
Session.set('progressDialogSettings', progressDialogSettings);
};
OHIF.studylist.progressDialog = {
update: _.debounce(updateProgress, 100),
setMessage: _.debounce(setProgressMessage, 100),
show: function(title, numberOfTotal) { show: function(title, numberOfTotal) {
Session.set('progressDialogSettings', { Session.set('progressDialogSettings', {
title: title, title: title,
@ -8,16 +32,6 @@ progressDialog = {
$('#progressDialog').css('display', 'block'); $('#progressDialog').css('display', 'block');
}, },
update: function(numberOfCompleted) {
var progressDialogSettings = Session.get('progressDialogSettings');
progressDialogSettings.numberOfCompleted = numberOfCompleted;
Session.set('progressDialogSettings', progressDialogSettings);
if (progressDialogSettings.numberOfCompleted === progressDialogSettings.numberOfTotal) {
progressDialog.close();
}
},
close: function() { close: function() {
Session.set('progressDialogSettings', { Session.set('progressDialogSettings', {
title: '', title: '',
@ -26,17 +40,12 @@ progressDialog = {
}); });
$('#progressDialog').css('display', 'none'); $('#progressDialog').css('display', 'none');
},
setMessage: function(message) {
let progressDialogSettings = Session.get('progressDialogSettings');
progressDialogSettings.message = message;
Session.set('progressDialogSettings', progressDialogSettings);
} }
}; };
Template.progressDialog.helpers({ Template.progressDialog.helpers({
progressDialogTitle() { progressDialogTitle() {
var settings = Session.get('progressDialogSettings'); const settings = Session.get('progressDialogSettings');
if (!settings) { if (!settings) {
return; return;
} }
@ -55,11 +64,11 @@ Template.progressDialog.helpers({
return; return;
} }
return settings.numberOfCompleted / settings.numberOfTotal; return (settings.numberOfCompleted / settings.numberOfTotal) * 100;
}, },
progressMessage() { progressMessage() {
var settings = Session.get('progressDialogSettings'); const settings = Session.get('progressDialogSettings');
if (!settings) { if (!settings) {
return; return;
} }

View File

@ -0,0 +1,5 @@
import './viewSeriesDetailsModal.html';
import './seriesDetailsTable/seriesDetailsTable.html';
import './seriesDetailsTable/seriesDetailsTable.styl';
import './seriesDetailsTable/seriesDetailsTable.js';

View File

@ -0,0 +1,15 @@
import './serverInformationDicomWeb/serverInformationDicomWeb.html';
import './serverInformationDicomWeb/serverInformationDicomWeb.js';
import './serverInformationDimse/serverInformationDimse.html';
import './serverInformationDimse/serverInformationDimse.js';
import './serverInformationForm/serverInformationForm.html';
import './serverInformationForm/serverInformationForm.js';
import './serverInformationList/serverInformationList.html';
import './serverInformationList/serverInformationList.js';
import './serverInformationModal/serverInformationModal.html';
import './serverInformationModal/serverInformationModal.styl';
import './serverInformationModal/serverInformationModal.js';

View File

@ -1,10 +0,0 @@
<template name="serverInformationFormField">
<div class="col-lg-6">
<div class="form-group">
<label class="wrapper">
<span>{{this.label}}</span>
<input type="text" name="{{this.name}}" class="form-control">
</label>
</div>
</div>
</template>

View File

@ -0,0 +1,2 @@
import './studyContextMenu.html';
import './studyContextMenu.js';

View File

@ -1,25 +1,12 @@
<template name="studyContextMenu"> <template name="studyContextMenu">
{{#if uiSettings.studyListFunctionsEnabled}} {{#dropdownForm}}
<div id="studyContextMenu" class="studyContextMenu noselect" <li><a id="viewSeriesDetails" type="button"
oncontextmenu='return false;' title="View Series Details"
unselectable='on' >View Series Details</a></li>
onselectstart='return false;'> <li class="disabled"><a class="disabled">Anonymize</a></li>
<ul> <li class="disabled"><a class="disabled">Send</a></li>
<li> <li class="divider" role="separator"></li>
<a id="viewSeriesDetails" type="button" <li class="disabled"><a class="disabled">Delete</a></li>
title="View Series Details"> <li><a id="exportSelectedStudies" title="Export Selected Studies">Export</a></li>
View Series Details {{/dropdownForm}}
</a>
<a class="disabled">Anonymize</a>
<a class="disabled">Send</a>
<hr/>
<a class="disabled">Delete</a>
<a id="exportSelectedStudies"
title="Export Selected Studies">
Export
</a>
</li>
</ul>
</div>
{{/if}}
</template> </template>

View File

@ -1,84 +1,48 @@
function closeHandler(dialog) { import { OHIF } from 'meteor/ohif:core';
// Hide the dialog
$(dialog).css('display', 'none');
// Remove the backdrop
$('.removableBackdrop').remove();
}
/** /**
* This function is used inside the StudyList package to define a right click callback * This function is used inside the StudyList package to define a right click callback
* *
* @param e * @param event
*/ */
openStudyContextMenu = function(e) { openStudyContextMenu = event => {
StudyList.functions['exportSelectedStudies'] = exportSelectedStudies; if (!OHIF.uiSettings.studyListFunctionsEnabled) {
StudyList.functions['viewSeriesDetails'] = viewSeriesDetails; return;
Template.studyContextMenu.study = $(e.currentTarget);
var dialog = $('#studyContextMenu');
// Show the nonTargetLesion dialog above
var dialogProperty = {
display: 'block'
};
var pageHeight = $(document).height();
dialogProperty.top = Math.max(e.pageY, 0);
dialogProperty.top = Math.min(dialogProperty.top, pageHeight - dialog.outerHeight());
var pageWidth = $(document).width();
dialogProperty.left = Math.max(e.pageX, 0);
dialogProperty.left = Math.min(dialogProperty.left, pageWidth - dialog.outerWidth());
// 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;
dialogProperty.margin = 'auto';
} }
dialog.css(dialogProperty); StudyList.functions.exportSelectedStudies = exportSelectedStudies;
dialog.focus(); StudyList.functions.viewSeriesDetails = viewSeriesDetails;
// Show the backdrop Template.studyContextMenu.$study = $(event.currentTarget);
UI.render(Template.removableBackdrop, document.body);
// Make sure the context menu is closed when the user clicks away const dropdown = OHIF.ui.showFormDropdown('studyContextMenu');
$('.removableBackdrop').one('mousedown touchstart', function() { const $dropdown = $(dropdown);
closeHandler(dialog); const $dropdownMenu = $dropdown.children('.dropdown-menu');
});
dropdown.oncontextmenu = () => false;
$dropdownMenu.css({
position: 'fixed',
left: `${event.clientX}px`,
top: `${event.clientY}px`,
'z-index': 10000
}).bounded().focus();
}; };
/** /**
* Exports all selected studies on the studylist * Exports all selected studies on the studylist
*/ */
function exportSelectedStudies() { function exportSelectedStudies() {
var selectedStudies = StudyListSelectedStudies.find({}, { const selectedStudies = OHIF.studylist.getSelectedStudies();
sort: {
studyDate: 1
}
}).fetch() || [];
exportStudies(selectedStudies); OHIF.studylist.exportStudies(selectedStudies);
} }
/** /**
* Display series details of study in modal * Display series details of study in modal
*/ */
function viewSeriesDetails() { function viewSeriesDetails() {
var selectedStudies = StudyListSelectedStudies.find({}, { const selectedStudies = OHIF.studylist.getSelectedStudies();
sort: {
studyDate: 1
}
}).fetch();
if (!selectedStudies) { if (!selectedStudies) {
return; return;
@ -91,7 +55,7 @@ function viewSeriesDetails() {
Template.studyContextMenu.events({ Template.studyContextMenu.events({
'click a': function(e) { 'click a': function(e) {
var study = Template.studyContextMenu.study; var study = Template.studyContextMenu.$study;
var id = $(e.currentTarget).attr('id'); var id = $(e.currentTarget).attr('id');
var fn = StudyList.functions[id]; var fn = StudyList.functions[id];
@ -100,6 +64,5 @@ Template.studyContextMenu.events({
} }
var dialog = $('#studyContextMenu'); var dialog = $('#studyContextMenu');
closeHandler(dialog);
} }
}); });

View File

@ -1,63 +0,0 @@
@import "{design}/app"
.studyContextMenu
z-index: 10000
display: none
position: absolute
color: #2f2f2f
background-color: #f2f2f2
width: -moz-fit-content
width: -webkit-fit-content
width: fit-content
width: 207px
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: 3px
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175)
box-shadow: 0 6px 12px rgba(0, 0, 0, .175)
hr
border-top: solid 1px #d1d1d1
margin: 2px
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
font-size: 15px
line-height: 1.7
color: black
&:not(.disabled):hover
theme('color', '$activeColor')
text-decoration: none
&.disabled
opacity: 0.4
pointer-events: none
cursor: default
.fa-lg
width: 40px
text-align: center

View File

@ -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';

View File

@ -14,6 +14,5 @@
{{#each templateName in additionalTemplates}} {{#each templateName in additionalTemplates}}
{{> UI.dynamic template=templateName}} {{> UI.dynamic template=templateName}}
{{/each}} {{/each}}
{{>studyContextMenu }}
{{>progressDialog}} {{>progressDialog}}
</template> </template>

View File

@ -19,7 +19,7 @@ Session.setDefault('activeContentId', 'studylistTab');
Template.studylist.onCreated(() => { Template.studylist.onCreated(() => {
const instance = Template.instance(); const instance = Template.instance();
if (StudyList.subscriptions) { if (StudyList.subscriptions) {
StudyList.subscriptions.forEach(subscriptionName => { StudyList.subscriptions.forEach(subscriptionName => {
instance.subscribe(subscriptionName); instance.subscribe(subscriptionName);
@ -44,18 +44,3 @@ Template.studylist.onRendered(() => {
Meteor.subscribe('hangingprotocols'); Meteor.subscribe('hangingprotocols');
}); });
Template.studylist.events({
'click #tablist a[data-toggle="tab"]': function(e) {
// If this tab is already active, do nothing
const tabButton = $(e.currentTarget);
const tabTitle = tabButton.parents('.tabTitle');
if (tabTitle.hasClass('active')) {
return;
}
// Otherwise, switch to the tab
const contentId = tabButton.data('target').replace('#', '');
switchToTab(contentId);
}
});

View File

@ -1,8 +0,0 @@
<template name="tabTitle">
<li role="presentation" class="tabTitle">
<a data-target="#{{contentid}}" role="tab" data-toggle="tab">
{{title}}
<button class="close">&times;</button>
</a>
</li>
</template>

View File

@ -1,42 +0,0 @@
Template.tabTitle.events({
/**
* Closes a tab when the close button is pressed in the title
* The next tab to the left is loaded if the current tab is closed
*
* @param e The click event used to close the tab
*/
'click .close': function(e) {
console.log('click .close');
// Identify the tab title DOM node
var tab = $(e.currentTarget).parents('a[data-toggle="tab"]').eq(0);
// Get the relevent contentId that this tab title represents
// Replace any hash marks (#) that were required by Bootstrap's tab switching
var contentId = tab.data('target').replace('#', '');
// Check if we are closing the active tab. If we are, prepare to switch
// to the next tab to the left.
var activeContentId = Session.get('activeContentId');
if (activeContentId === contentId) {
// Find the index of the tab that is being closed
var tabIndex = tab.parent('li').index();
// Find the index the tab to its left
var newActiveTabIndex = Math.max(tabIndex - 1, 0);
// Find the DOM node of the tab that will be activated
var newActiveTab = $('.tabTitle').eq(newActiveTabIndex);
// Find the content ID of the tab that will be switched to
var newActiveTabLink = newActiveTab.find('a[data-toggle=tab]');
var newContentId = newActiveTabLink.data('target').replace('#', '');
// Switch to this tab
switchToTab(newContentId);
}
// Remove any stored data related to this tab from the global ViewerData structure
delete ViewerData[contentId];
}
});

View File

@ -1,4 +0,0 @@
.tabTitle
button.close
// Prevent blue glow around button in Chrome
outline: none

View File

@ -0,0 +1,3 @@
import './collections';
import './lib';
import './components';

View File

@ -1,10 +1,13 @@
var exportFailed; import { JSZip } from 'meteor/silentcicero:jszip';
import { OHIF } from 'meteor/ohif:core';
let exportFailed;
/** /**
* Exports requested studies * Exports requested studies
* @param studiesToExport Studies to export * @param studiesToExport Studies to export
*/ */
exportStudies = function(studiesToExport) { OHIF.studylist.exportStudies = studiesToExport => {
if (studiesToExport.length < 1) { if (studiesToExport.length < 1) {
return; return;
} }
@ -12,37 +15,45 @@ exportStudies = function(studiesToExport) {
queryStudies(studiesToExport, exportQueriedStudies); queryStudies(studiesToExport, exportQueriedStudies);
}; };
function exportQueriedStudies(studiesToExport) { const exportQueriedStudies = studiesToExport => {
var numberOfFilesToExport = 0; let numberOfFilesToExport = 0;
studiesToExport.forEach(function(study) { studiesToExport.forEach(study => {
numberOfFilesToExport += getNumberOfFilesInStudy(study); numberOfFilesToExport += getNumberOfFilesInStudy(study);
}); });
progressDialog.show("Exporting Studies...", numberOfFilesToExport); OHIF.studylist.progressDialog.show('Exporting Studies...', numberOfFilesToExport);
try { try {
exportQueriedStudiesInternal(studiesToExport, numberOfFilesToExport); exportQueriedStudiesInternal(studiesToExport, numberOfFilesToExport);
} catch (err) { } catch (err) {
progressDialog.close(); OHIF.studylist.progressDialog.close();
console.error("Failed to export studies: " + err.message); OHIF.log.error(`Failed to export studies: ${err.message}`);
} }
} };
function exportQueriedStudiesInternal(studiesToExport, numberOfFilesToExport) { const exportQueriedStudiesInternal = (studiesToExport, numberOfFilesToExport) => {
var zip = new JSZip(); const zip = new JSZip();
exportFailed = false; exportFailed = false;
var numberOfFilesExported = 0; let numberOfFilesExported = 0;
studiesToExport.forEach(function(study) { const onExportFailed = err => {
exportFailed = true;
OHIF.studylist.progressDialog.close();
//TODO: Export failed and dialog closed, so let user know
OHIF.log.error('Failed to export studies!', err);
};
studiesToExport.forEach(study => {
sortStudy(study); sortStudy(study);
var studyFolder = zip.folder(study.studyInstanceUid); const studyFolder = zip.folder(study.studyInstanceUid);
study.seriesList.forEach(function(series) { study.seriesList.forEach(series => {
var seriesFolder = studyFolder.folder(series.seriesInstanceUid); const seriesFolder = studyFolder.folder(series.seriesInstanceUid);
series.instances.forEach(function(instance) { series.instances.forEach(instance => {
if (!instance.wadouri) { if (!instance.wadouri) {
return; return;
} }
@ -53,12 +64,12 @@ function exportQueriedStudiesInternal(studiesToExport, numberOfFilesToExport) {
} }
// Download and Zip the dicom file // Download and Zip the dicom file
var xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open("GET", instance.wadouri, true); xhr.open('GET', instance.wadouri, true);
xhr.responseType = "blob"; xhr.responseType = 'blob';
// Downloaded the dicom file completely // Downloaded the dicom file completely
xhr.onload = function(e) { xhr.onload = () => {
// If failed to download a dicom file, skip others // If failed to download a dicom file, skip others
if (exportFailed) { if (exportFailed) {
return; return;
@ -66,17 +77,17 @@ function exportQueriedStudiesInternal(studiesToExport, numberOfFilesToExport) {
// Failed to export a file // Failed to export a file
if (xhr.readyState === 4 && xhr.status !== 200) { if (xhr.readyState === 4 && xhr.status !== 200) {
onExportFailed("File not downloaded: " + instance.wadouri); onExportFailed(`File not downloaded: ${instance.wadouri}`);
return; return;
} }
var blobFile = new Blob([xhr.response], {type: 'application/dicom'}); const blobFile = new Blob([xhr.response], { type: 'application/dicom' });
var fileReader = new FileReader(); const fileReader = new FileReader();
fileReader.onload = function() { fileReader.onload = () => {
try { try {
seriesFolder.file(instance.sopInstanceUid + ".dcm", fileReader.result, { binary: true }); seriesFolder.file(`${instance.sopInstanceUid}.dcm`, fileReader.result, { binary: true });
} catch(err) { } catch(err) {
onExportFailed(err.message); onExportFailed(err.message);
return; return;
@ -85,31 +96,21 @@ function exportQueriedStudiesInternal(studiesToExport, numberOfFilesToExport) {
numberOfFilesExported++; numberOfFilesExported++;
if (numberOfFilesExported === numberOfFilesToExport) { if (numberOfFilesExported === numberOfFilesToExport) {
var zipContent = zip.generate({ type: "blob" }); const zipContent = zip.generate({ type: 'blob' });
saveAs(zipContent, "studies.zip"); saveAs(zipContent, 'studies.zip');
} }
progressDialog.update(numberOfFilesExported); OHIF.studylist.progressDialog.update(numberOfFilesExported);
}; };
fileReader.readAsArrayBuffer(blobFile); fileReader.readAsArrayBuffer(blobFile);
}; };
// Failed to download the dicom file // Failed to download the dicom file
xhr.onerror = function() { xhr.onerror = () => onExportFailed(`File not downloaded: ${instance.wadouri}`);
onExportFailed("File not downloaded: " + instance.wadouri);
};
xhr.send(); xhr.send();
}); });
}); });
}); });
} };
function onExportFailed(err) {
exportFailed = true;
progressDialog.close();
//TODO: Export failed and dialog closed, so let user know
console.error("Failed to export studies!", err);
}

View File

@ -0,0 +1,9 @@
import { OHIF } from 'meteor/ohif:core';
OHIF.studylist.getSelectedStudies = () => {
return StudyListSelectedStudies.find({}, {
sort: {
studyDate: 1
}
}).fetch() || [];
};

View File

@ -1,3 +1,4 @@
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core'; import { OHIF } from 'meteor/ohif:core';
/** /**
@ -10,7 +11,7 @@ import { OHIF } from 'meteor/ohif:core';
* @param doneCallback The callback function to be executed when the study retrieval has finished * @param doneCallback The callback function to be executed when the study retrieval has finished
* @param failCallback The callback function to be executed when the study retrieval has failed * @param failCallback The callback function to be executed when the study retrieval has failed
*/ */
getStudiesMetadata = function(studyInstanceUids, doneCallback, failCallback) { OHIF.studylist.getStudiesMetadata = (studyInstanceUids, doneCallback, failCallback) => {
// Check to make sure studyInstanceUids were actually input // Check to make sure studyInstanceUids were actually input
if (!studyInstanceUids || !studyInstanceUids.length) { if (!studyInstanceUids || !studyInstanceUids.length) {
if (failCallback && typeof failCallback === 'function') { if (failCallback && typeof failCallback === 'function') {
@ -22,13 +23,13 @@ getStudiesMetadata = function(studyInstanceUids, doneCallback, failCallback) {
// Create an empty array to store the Promises for each metaData // Create an empty array to store the Promises for each metaData
// retrieval call // retrieval call
var promises = []; const promises = [];
// Loop through the array of studyInstanceUids // Loop through the array of studyInstanceUids
studyInstanceUids.forEach(function(studyInstanceUid) { studyInstanceUids.forEach(function(studyInstanceUid) {
// Create a new Deferred to monitor the progress of the asynchronous // Create a new Deferred to monitor the progress of the asynchronous
// metaData retrieval // metaData retrieval
var deferred = new $.Deferred(); const deferred = new $.Deferred();
// Send the call, and attach doneCallbacks and failCallbacks // Send the call, and attach doneCallbacks and failCallbacks
// which can resolve or reject the related promise based on its outcome // which can resolve or reject the related promise based on its outcome
@ -45,7 +46,7 @@ getStudiesMetadata = function(studyInstanceUids, doneCallback, failCallback) {
// When all of the promises are complete, this callback runs // When all of the promises are complete, this callback runs
$.when.apply($, promises).done(function() { $.when.apply($, promises).done(function() {
// Convert the Arguments Array-like Object to an actual array // Convert the Arguments Array-like Object to an actual array
var studies = $.makeArray(arguments); const studies = $.makeArray(arguments);
// Pass the studies array to the doneCallback, if one exists // Pass the studies array to the doneCallback, if one exists
if (doneCallback && typeof doneCallback === 'function') { if (doneCallback && typeof doneCallback === 'function') {

View File

@ -14,7 +14,7 @@ importStudies = function(filesToImport, importCallback) {
}; };
var numberOfFilesToUpload = filesToImport.length; var numberOfFilesToUpload = filesToImport.length;
var studiesToImport = []; var studiesToImport = [];
progressDialog.show({ OHIF.studylist.progressDialog.show({
title: "Uploading Files...", title: "Uploading Files...",
numberOfCompleted: 0, numberOfCompleted: 0,
numberOfTotal: numberOfFilesToUpload numberOfTotal: numberOfFilesToUpload
@ -38,7 +38,7 @@ importStudies = function(filesToImport, importCallback) {
updateFileUploadStatus(fileUploadStatus, true); updateFileUploadStatus(fileUploadStatus, true);
var numberOfFilesProcessedToUpload = fileUploadStatus.numberOfFilesUploaded + fileUploadStatus.numberOfFilesFailed; var numberOfFilesProcessedToUpload = fileUploadStatus.numberOfFilesUploaded + fileUploadStatus.numberOfFilesFailed;
progressDialog.update(numberOfFilesProcessedToUpload); OHIF.studylist.progressDialog.update(numberOfFilesProcessedToUpload);
if (numberOfFilesToUpload === numberOfFilesProcessedToUpload) { if (numberOfFilesToUpload === numberOfFilesProcessedToUpload) {
// The upload is completed, so import files // The upload is completed, so import files
@ -74,7 +74,7 @@ function importStudiesInternal(studiesToImport, importCallback) {
var numberOfStudiesToImport = studiesToImport.length; var numberOfStudiesToImport = studiesToImport.length;
progressDialog.show({ OHIF.studylist.progressDialog.show({
title: "Importing Studies...", title: "Importing Studies...",
numberOfCompleted: 0, numberOfCompleted: 0,
numberOfTotal: numberOfStudiesToImport numberOfTotal: numberOfStudiesToImport
@ -84,13 +84,13 @@ function importStudiesInternal(studiesToImport, importCallback) {
Meteor.call("createStudyImportStatus", function(err, studyImportStatusId) { Meteor.call("createStudyImportStatus", function(err, studyImportStatusId) {
if (err) { if (err) {
// Hide dialog // Hide dialog
progressDialog.close(); OHIF.studylist.progressDialog.close();
console.log(err.message); console.log(err.message);
return; return;
} }
// Handle when StudyImportStatus collection is updated // Handle when StudyImportStatus collection is updated
StudyImportStatus.find(studyImportStatusId).observe({ OHIF.studylist.collections.StudyImportStatus.find(studyImportStatusId).observe({
changed: function(studyImportStatus) { changed: function(studyImportStatus) {
if (!studyImportStatus) { if (!studyImportStatus) {
return; return;
@ -100,16 +100,16 @@ function importStudiesInternal(studiesToImport, importCallback) {
// Show number of imported files // Show number of imported files
var successMessage = 'Imported '+studyImportStatus.numberOfStudiesImported+' of '+numberOfStudiesToImport; var successMessage = 'Imported '+studyImportStatus.numberOfStudiesImported+' of '+numberOfStudiesToImport;
progressDialog.setMessage({ OHIF.studylist.progressDialog.setMessage({
message: successMessage, message: successMessage,
messageType: 'success' messageType: 'success'
}); });
progressDialog.update(numberOfStudiesProcessedToImport); OHIF.studylist.progressDialog.update(numberOfStudiesProcessedToImport);
// Show number of failed files if there is at least one failed file // Show number of failed files if there is at least one failed file
if (studyImportStatus.numberOfStudiesFailed > 0) { if (studyImportStatus.numberOfStudiesFailed > 0) {
var successMessage = 'Failed '+studyImportStatus.numberOfStudiesFailed+' of '+numberOfStudiesToImport; var successMessage = 'Failed '+studyImportStatus.numberOfStudiesFailed+' of '+numberOfStudiesToImport;
progressDialog.setMessage({ OHIF.studylist.progressDialog.setMessage({
message: successMessage, message: successMessage,
messageType: 'warning' messageType: 'warning'
}); });

View File

@ -0,0 +1,11 @@
import './third-party/jquery.twbsPagination.min.js';
import './exportStudies.js';
import './getSelectedStudies.js';
import './getStudyMetadata.js';
import './getStudiesMetadata.js';
import './importStudies.js';
import './openNewTab.js';
import './queryStudies.js';
import './studylist.js';
import './switchToTab.js';

View File

@ -1,3 +1,5 @@
import { OHIF } from 'meteor/ohif:core';
/** /**
* Queries requested studies to get their metadata from PACS * Queries requested studies to get their metadata from PACS
* @param studiesToQuery Studies to query * @param studiesToQuery Studies to query
@ -11,7 +13,7 @@ queryStudies = function(studiesToQuery, doneCallback) {
var studiesQueried = []; var studiesQueried = [];
var numberOfStudiesToQuery = studiesToQuery.length; var numberOfStudiesToQuery = studiesToQuery.length;
progressDialog.show("Querying Studies...", numberOfStudiesToQuery); OHIF.studylist.progressDialog.show("Querying Studies...", numberOfStudiesToQuery);
studiesToQuery.forEach(function(studyToQuery) { studiesToQuery.forEach(function(studyToQuery) {
getStudyMetadata(studyToQuery.studyInstanceUid, function(study) { getStudyMetadata(studyToQuery.studyInstanceUid, function(study) {
@ -19,7 +21,7 @@ queryStudies = function(studiesToQuery, doneCallback) {
var numberOfStudiesQueried = studiesQueried.length; var numberOfStudiesQueried = studiesQueried.length;
progressDialog.update(numberOfStudiesQueried); OHIF.studylist.progressDialog.update(numberOfStudiesQueried);
if (numberOfStudiesQueried === numberOfStudiesToQuery) { if (numberOfStudiesQueried === numberOfStudiesToQuery) {
doneCallback(studiesQueried); doneCallback(studiesQueried);
@ -45,4 +47,4 @@ getNumberOfFilesInStudy = function(study) {
}); });
return numberOFFilesToExport; return numberOFFilesToExport;
}; };

View File

@ -13,9 +13,6 @@ switchToTab = function(contentId) {
OHIF.log.info('Switching to tab: ' + contentId); OHIF.log.info('Switching to tab: ' + contentId);
$('.tabTitle').removeClass('active');
$('.tabTitle a[data-target="#' + contentId + '"]').addClass('active');
$('.tab-content .tab-pane').removeClass('active'); $('.tab-content .tab-pane').removeClass('active');
if (contentId !== 'studylistTab') { if (contentId !== 'studylistTab') {
$('.tab-content .tab-pane#viewerTab').addClass('active'); $('.tab-content .tab-pane#viewerTab').addClass('active');
@ -71,7 +68,7 @@ switchToTab = function(contentId) {
var studyInstanceUids = ViewerData[contentId].studyInstanceUids; var studyInstanceUids = ViewerData[contentId].studyInstanceUids;
// Attempt to retrieve the meta data (it might be cached) // Attempt to retrieve the meta data (it might be cached)
getStudiesMetadata(studyInstanceUids, function(studies) { OHIF.studylist.getStudiesMetadata(studyInstanceUids, function(studies) {
viewStudiesInTab(contentId, studies); viewStudiesInTab(contentId, studies);
}); });
} }

View File

@ -1,10 +1,10 @@
Package.describe({ Package.describe({
name: "ohif:study-list", name: 'ohif:study-list',
summary: "Basic study list for web-based DICOM viewers", summary: 'Basic study list for web-based DICOM viewers',
version: '0.0.1' version: '0.0.1'
}); });
Package.onUse(function (api) { Package.onUse(function(api) {
api.versionsFrom('1.4'); api.versionsFrom('1.4');
api.use('ecmascript'); api.use('ecmascript');
@ -34,109 +34,14 @@ Package.onUse(function (api) {
// TODO: Replace with NPM dependency // TODO: Replace with NPM dependency
api.use('ohif:cornerstone'); // Only for HammerJS api.use('ohif:cornerstone'); // Only for HammerJS
api.addFiles('both/collections.js', ['client', 'server']); // Client and server imports
api.addFiles('both/schema/servers.js', ['client', 'server']); api.addFiles('both/index.js', [ 'client', 'server' ]);
api.addFiles('both/lib/getCurrentServer.js', ['client', 'server']);
// Client collections and subscriptions // Server imports
api.addFiles('client/collections/subscriptions.js', 'client'); api.addFiles('server/index.js', 'server');
// Components // Client imports
api.addFiles('client/components/studylist.html', 'client'); api.addFiles('client/index.js', 'client');
api.addFiles('client/components/studylist.js', 'client');
api.addFiles('client/components/studylist.styl', 'client');
api.addFiles('client/components/tabTitle/tabTitle.html', 'client');
api.addFiles('client/components/tabTitle/tabTitle.js', 'client');
api.addFiles('client/components/tabTitle/tabTitle.styl', 'client');
api.addFiles('client/components/studylistStudy/studylistStudy.html', 'client');
api.addFiles('client/components/studylistStudy/studylistStudy.js', 'client');
api.addFiles('client/components/studylistStudy/studylistStudy.styl', 'client');
api.addFiles('client/components/studylistResult/studylistResult.html', 'client');
api.addFiles('client/components/studylistResult/studylistResult.js', 'client');
api.addFiles('client/components/studylistResult/studylistResult.styl', 'client');
api.addFiles('client/components/studyContextMenu/studyContextMenu.html', 'client');
api.addFiles('client/components/studyContextMenu/studyContextMenu.js', 'client');
api.addFiles('client/components/studyContextMenu/studyContextMenu.styl', 'client');
api.addFiles('client/components/studylistToolbar/studylistToolbar.html', 'client');
api.addFiles('client/components/studylistToolbar/studylistToolbar.js', 'client');
api.addFiles('client/components/studylistToolbar/studylistToolbar.styl', 'client');
api.addFiles('client/components/progressDialog/progressDialog.html', 'client');
api.addFiles('client/components/progressDialog/progressDialog.styl', 'client');
api.addFiles('client/components/progressDialog/progressDialog.js', 'client');
api.addFiles('client/components/studylistPagination/studylistPagination.html', 'client');
api.addFiles('client/components/studylistPagination/studylistPagination.styl', 'client');
api.addFiles('client/components/studylistPagination/studylistPagination.js', 'client');
api.addFiles('client/components/viewSeriesDetailsModal/viewSeriesDetailsModal.html', 'client');
api.addFiles('client/components/seriesDetailsTable/seriesDetailsTable.html', 'client');
api.addFiles('client/components/seriesDetailsTable/seriesDetailsTable.styl', 'client');
api.addFiles('client/components/seriesDetailsTable/seriesDetailsTable.js', 'client');
api.addFiles('client/components/serverInformation/serverInformationDicomWeb/serverInformationDicomWeb.html', 'client');
api.addFiles('client/components/serverInformation/serverInformationDicomWeb/serverInformationDicomWeb.js', 'client');
api.addFiles('client/components/serverInformation/serverInformationDimse/serverInformationDimse.html', 'client');
api.addFiles('client/components/serverInformation/serverInformationDimse/serverInformationDimse.js', 'client');
api.addFiles('client/components/serverInformation/serverInformationForm/serverInformationForm.html', 'client');
api.addFiles('client/components/serverInformation/serverInformationForm/serverInformationForm.js', 'client');
api.addFiles('client/components/serverInformation/serverInformationForm/serverInformationFormField.html', 'client');
api.addFiles('client/components/serverInformation/serverInformationList/serverInformationList.html', 'client');
api.addFiles('client/components/serverInformation/serverInformationList/serverInformationList.js', 'client');
api.addFiles('client/components/serverInformation/serverInformationModal/serverInformationModal.html', 'client');
api.addFiles('client/components/serverInformation/serverInformationModal/serverInformationModal.styl', 'client');
api.addFiles('client/components/serverInformation/serverInformationModal/serverInformationModal.js', 'client');
// Client-side library functions
api.addFiles('client/lib/getStudyMetadata.js', 'client');
api.addFiles('client/lib/getStudiesMetadata.js', 'client');
api.addFiles('client/lib/openNewTab.js', 'client');
api.addFiles('client/lib/switchToTab.js', 'client');
api.addFiles('client/lib/exportStudies.js', 'client');
api.addFiles('client/lib/studylist.js', 'client');
api.addFiles('client/lib/queryStudies.js', 'client');
api.addFiles('client/lib/importStudies.js', 'client');
api.addFiles('client/lib/jquery.twbsPagination/jquery.twbsPagination.min.js', 'client');
// Server-side functions
api.addFiles('server/publications.js', 'server');
api.addFiles('server/validateServerConfiguration.js', 'server');
api.addFiles('server/servers.js', 'server');
api.addFiles('server/lib/remoteGetValue.js', 'server');
api.addFiles('server/lib/encodeQueryData.js', 'server');
api.addFiles('server/lib/parseFloatArray.js', 'server');
api.addFiles('server/methods/getStudyMetadata.js', 'server');
api.addFiles('server/methods/importStudies.js', 'server');
api.addFiles('server/methods/studylistSearch.js', 'server');
api.addFiles('server/services/namespace.js', 'server');
// DICOMWeb instance, study, and metadata retrieval
api.addFiles('server/services/qido/instances.js', 'server');
api.addFiles('server/services/qido/studies.js', 'server');
api.addFiles('server/services/wado/retrieveMetadata.js', 'server');
// DIMSE instance, study, and metadata retrieval
api.addFiles('server/services/dimse/instances.js', 'server');
api.addFiles('server/services/dimse/studies.js', 'server');
api.addFiles('server/services/dimse/retrieveMetadata.js', 'server');
api.addFiles('server/services/dimse/setup.js', 'server');
// Study, instance, and metadata retrieval from remote PACS via Orthanc as a proxy
api.addFiles('server/services/remote/instances.js', 'server');
api.addFiles('server/services/remote/studies.js', 'server');
api.addFiles('server/services/remote/retrieveMetadata.js', 'server');
// Export Servers and CurrentServer Collections // Export Servers and CurrentServer Collections
api.export('Servers', ['client', 'server']); api.export('Servers', ['client', 'server']);
@ -149,10 +54,8 @@ Package.onUse(function (api) {
// Export StudyList helper functions for usage in Routes // Export StudyList helper functions for usage in Routes
api.export('getStudyMetadata', 'client'); api.export('getStudyMetadata', 'client');
api.export('getStudiesMetadata', 'client');
api.export('openNewTab', 'client'); api.export('openNewTab', 'client');
api.export('switchToTab', 'client'); api.export('switchToTab', 'client');
api.export('progressDialog', 'client');
api.export('StudyList'); api.export('StudyList');
// Export the global ViewerData object // Export the global ViewerData object
@ -160,5 +63,5 @@ Package.onUse(function (api) {
// Export the Collections // Export the Collections
api.export('StudyListStudies', 'client'); api.export('StudyListStudies', 'client');
api.export('StudyListSelectedStudies', 'client') api.export('StudyListSelectedStudies', 'client');
}); });

View File

@ -0,0 +1,6 @@
import './publications.js';
import './validateServerConfiguration.js';
import './lib';
import './methods';
import './services';

View File

@ -0,0 +1,2 @@
import './remoteGetValue.js';
import './encodeQueryData.js';

View File

@ -1,4 +1,4 @@
import { StudyImportStatus } from '../../both/collections'; import { OHIF } from 'meteor/ohif:core';
var fs = Npm.require('fs'); var fs = Npm.require('fs');
var fiber = Npm.require('fibers'); var fiber = Npm.require('fibers');
@ -73,14 +73,14 @@ Meteor.methods({
*/ */
createStudyImportStatus: function() { createStudyImportStatus: function() {
var studyImportStatus = { numberOfStudiesImported: 0, numberOfStudiesFailed: 0 }; var studyImportStatus = { numberOfStudiesImported: 0, numberOfStudiesFailed: 0 };
return StudyImportStatus.insert(studyImportStatus); return OHIF.studylist.collections.StudyImportStatus.insert(studyImportStatus);
}, },
/** /**
* Remove the study import status item from the collection * Remove the study import status item from the collection
* @param id Collection id of the study import status in the collection * @param id Collection id of the study import status in the collection
*/ */
removeStudyImportStatus: function(id) { removeStudyImportStatus: function(id) {
StudyImportStatus.remove(id); OHIF.studylist.collections.StudyImportStatus.remove(id);
} }
}); });
@ -96,16 +96,16 @@ function importStudiesDIMSE(studiesToImport, studyImportStatusId) {
try { try {
// Update the import status // Update the import status
if (err) { if (err) {
StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}}); OHIF.studylist.collections.StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}});
console.log("Failed to import study via DIMSE: ", file, err); console.log("Failed to import study via DIMSE: ", file, err);
} else { } else {
StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesImported': 1}}); OHIF.studylist.collections.StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesImported': 1}});
console.log("Study successfully imported via DIMSE: ", file); console.log("Study successfully imported via DIMSE: ", file);
} }
} catch(error) { } catch(error) {
StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}}); OHIF.studylist.collections.StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}});
console.log("Failed to import study via DIMSE: ", file, error); console.log("Failed to import study via DIMSE: ", file, error);
} finally { } finally {
// The import operation of this file is completed, so delete it if still exists // The import operation of this file is completed, so delete it if still exists
@ -116,7 +116,7 @@ function importStudiesDIMSE(studiesToImport, studyImportStatusId) {
}).run(); }).run();
} catch(error) { } catch(error) {
StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}}); OHIF.studylist.collections.StudyImportStatus.update({_id: studyImportStatusId}, {$inc: {'numberOfStudiesFailed': 1}});
console.log("Failed to import study via DIMSE: ", file, error); console.log("Failed to import study via DIMSE: ", file, error);
} }

View File

@ -0,0 +1,3 @@
import './getStudyMetadata.js';
import './importStudies.js';
import './studylistSearch.js';

View File

@ -1,5 +1,8 @@
import { Meteor } from 'meteor/meteor';
import { OHIF } from 'meteor/ohif:core';
Meteor.publish('studyImportStatus', () => { Meteor.publish('studyImportStatus', () => {
return StudyImportStatus.find(); return OHIF.studylist.collections.StudyImportStatus.find();
}); });
Meteor.publish('servers', () => { Meteor.publish('servers', () => {

View File

@ -0,0 +1,16 @@
import './namespace.js';
// DICOMWeb instance, study, and metadata retrieval
import './qido/instances.js';
import './qido/studies.js';
import './wado/retrieveMetadata.js';
// DIMSE instance, study, and metadata retrieval
import './dimse/instances.js';
import './dimse/studies.js';
import './dimse/retrieveMetadata.js';
// Study, instance, and metadata retrieval from remote PACS via Orthanc as a proxy
import './remote/instances.js';
import './remote/studies.js';
import './remote/retrieveMetadata.js';

View File

@ -183,7 +183,6 @@ Package.onUse(function(api) {
api.export('updateAllViewports', 'client'); api.export('updateAllViewports', 'client');
api.export('queryStudies', 'client'); api.export('queryStudies', 'client');
api.export('getNumberOfFilesInStudy', 'client'); api.export('getNumberOfFilesInStudy', 'client');
api.export('exportStudies', 'client');
api.export('importStudies', 'client'); api.export('importStudies', 'client');
api.export('getActiveViewportElement', 'client'); api.export('getActiveViewportElement', 'client');
api.export('getInstanceClassDefaultViewport', 'client'); api.export('getInstanceClassDefaultViewport', 'client');