diff --git a/LesionTracker/.meteor/packages b/LesionTracker/.meteor/packages index 9dceea041..063656cd3 100644 --- a/LesionTracker/.meteor/packages +++ b/LesionTracker/.meteor/packages @@ -64,3 +64,4 @@ fastclick@1.0.12 standard-minifier-css@1.1.8 standard-minifier-js@1.1.8 johdirr:meteor-git-rev +u2622:persistent-session diff --git a/LesionTracker/.meteor/versions b/LesionTracker/.meteor/versions index 7d4b6c902..91c298cc5 100644 --- a/LesionTracker/.meteor/versions +++ b/LesionTracker/.meteor/versions @@ -3,6 +3,7 @@ accounts-password@1.2.12 aldeed:simple-schema@1.5.3 aldeed:template-extension@4.0.0 allow-deny@1.0.5 +amplify@1.0.0 anti:gagarin@0.4.11 anti:i18n@0.4.3 arsnebula:reactive-promise@0.9.1 @@ -124,6 +125,7 @@ tracker@1.1.0 twbs:bootstrap@3.3.6 typ:accounts-ldap@1.0.1 typ:ldapjs@0.7.3 +u2622:persistent-session@0.4.4 ui@1.0.11 underscore@1.0.9 url@1.0.10 diff --git a/OHIFViewer/.meteor/packages b/OHIFViewer/.meteor/packages index 025469895..2cf867dd9 100755 --- a/OHIFViewer/.meteor/packages +++ b/OHIFViewer/.meteor/packages @@ -42,3 +42,4 @@ standard-minifier-js@1.1.8 aldeed:simple-schema johdirr:meteor-git-rev wadoproxy +aldeed:template-extension diff --git a/OHIFViewer/.meteor/versions b/OHIFViewer/.meteor/versions index 11a52834d..56784812e 100755 --- a/OHIFViewer/.meteor/versions +++ b/OHIFViewer/.meteor/versions @@ -1,4 +1,5 @@ aldeed:simple-schema@1.5.3 +aldeed:template-extension@4.0.0 allow-deny@1.0.5 arsnebula:reactive-promise@0.9.1 autoupdate@1.3.11 diff --git a/Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.js b/Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.js deleted file mode 100644 index 388677bf0..000000000 --- a/Packages/lesiontracker/client/components/lastLoginModal/lastLoginModal.js +++ /dev/null @@ -1,9 +0,0 @@ -Template.lastLoginModal.helpers({ - lastLoginDate: function() { - var userLoginDate = ReactiveMethod.call('getPriorLoginDate'); - if (userLoginDate && userLoginDate.priorLoginDate) { - return moment(userLoginDate.priorLoginDate).format("MMMM Do YYYY, HH:mm:ss A"); - } - return; - } -}); \ No newline at end of file diff --git a/Packages/lesiontracker/client/components/userAccountMenu/userAccountMenu.js b/Packages/lesiontracker/client/components/userAccountMenu/userAccountMenu.js index 58ec7edaf..6d75389fb 100644 --- a/Packages/lesiontracker/client/components/userAccountMenu/userAccountMenu.js +++ b/Packages/lesiontracker/client/components/userAccountMenu/userAccountMenu.js @@ -1,3 +1,10 @@ +import { Template } from 'meteor/templating'; +import { ReactiveVar } from 'meteor/reactive-var'; +import { Session } from 'meteor/session'; + +// Display the last login modal as default +Session.setDefault('displayLastLoginModal', true); + Template.userAccountMenu.helpers({ name: function() { var nameSplit = Meteor.user().profile.fullName.split(' '); @@ -29,68 +36,87 @@ Template.userAccountMenu.events({ $('#serverInformationModal').modal('show'); }, 'click #logoutButton': function() { - // Remove reviewers info for the user - Meteor.call('removeUserFromReviewers', Meteor.userId()); - Meteor.logout(function() { Router.go('/entrySignIn'); }); } }); -Template.userAccountMenu.onRendered(function() { - var oldUserId; - var lastLoginModalInterval; +Template.userAccountMenu.onCreated(function userAccountMenuCreated() { + const instance = Template.instance(); - this.autorun(function() { - // Hook login/logout - var user = Meteor.user(); - if (!user) { + // Create reactive last login date + instance.lastLoginDate = new ReactiveVar(); + + // We need oldUser to remove Reviewers if the user logged out + let oldUser; + + // Get last login date + Meteor.call('getPriorLoginDate', function(error, lastLoginDate){ + if (error) { + console.log(error); return; } - var newUserId = user._id; + // Format the last login date + const formattedLastLoginDate = moment(lastLoginDate).format("MMMM Do YYYY, HH:mm:ss A"); - if (oldUserId === null && newUserId) { - Session.set('showLastLoginModal', true); + instance.lastLoginDate.set(formattedLastLoginDate); - // Log - HipaaLogger.logEvent({ - eventType: 'init', - userId: user._id, - userName: user.fullName - }); + }); - } else if (newUserId === null && oldUserId) { - // Set showLastLoginModal as null - Session.set('showLastLoginModal', null); - - // Destroy interval for last login modal - Meteor.clearInterval(lastLoginModalInterval); - console.log('The user logged out'); - - // Log - // TODO: eventType is not defined for logout in hipaa-audit-log - /*HipaaLogger.logEvent({ - eventType: 'logout', - userId: oldUserId, - userName: userName - });*/ - - // Remove the user from Reviewers - Meteor.call('removeUserFromReviewers', oldUserId); + instance.autorun(function() { + const lastLoginDate = instance.lastLoginDate.get(); + if (!lastLoginDate) { + return; } - oldUserId = Meteor.userId(); + // Hook login/logout + var user = Meteor.user(); + if (!user) { + // Display last login modal for the next login + Session.setPersistent('displayLastLoginModal', true); + + if (oldUser) { + // Log Signout + // TODO: eventType for logout is not defined + // HipaaLogger.logEvent({ + // eventType: 'logout', + // userId: oldUser._id, + // userName: oldUser.profile.fullName + // }); + + // Remove the user by oldUserId from Reviewers + Meteor.call('removeUserFromReviewers', oldUser._id); + } + return; + } + + // Set oldUser + oldUser = user; // Trigger last login date popup - if (Session.get('showLastLoginModal')) { - Modal.show('lastLoginModal'); - - lastLoginModalInterval = Meteor.setInterval(function() { - Modal.hide('lastLoginModal'); - Session.set('showLastLoginModal', null); - }, 3000); + if (!Session.get('displayLastLoginModal')) { + return; } + + // Displaye the modal + Modal.show('lastLoginModal', { + lastLoginDate: lastLoginDate + }); + + // Hide the modal after 5sec + Meteor.setTimeout(() => { + Modal.hide('lastLoginModal'); + Session.setPersistent('displayLastLoginModal', false); + }, 5000); + + // Log signin + HipaaLogger.logEvent({ + eventType: 'init', + userId: user._id, + userName: user.profile.fullName + }); + }); }); \ No newline at end of file diff --git a/Packages/lesiontracker/package.js b/Packages/lesiontracker/package.js index ad16e3a8a..9f06f4a55 100644 --- a/Packages/lesiontracker/package.js +++ b/Packages/lesiontracker/package.js @@ -197,7 +197,6 @@ Package.onUse(function(api) { api.addFiles('client/components/confirmRemoveTimepointAssociation/confirmRemoveTimepointAssociation.js', 'client'); api.addFiles('client/components/lastLoginModal/lastLoginModal.html', 'client'); - api.addFiles('client/components/lastLoginModal/lastLoginModal.js', 'client'); api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.html', 'client'); api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.styl', 'client'); diff --git a/Packages/lesiontracker/server/reviewers.js b/Packages/lesiontracker/server/reviewers.js index 27aa79d84..fb228c166 100644 --- a/Packages/lesiontracker/server/reviewers.js +++ b/Packages/lesiontracker/server/reviewers.js @@ -85,7 +85,13 @@ Meteor.methods({ }, getPriorLoginDate: function() { - return Meteor.users.findOne({_id: Meteor.userId()}, {fields: {priorLoginDate: 1}}); + return Meteor.users.findOne({ + _id: Meteor.userId() + }, { + fields: { + priorLoginDate: 1 + } + }).priorLoginDate; } }); diff --git a/Packages/viewerbase/client/components/viewer/toolbarSectionTools/toolbarSectionTools.styl b/Packages/viewerbase/client/components/viewer/toolbarSectionTools/toolbarSectionTools.styl index c618e0283..27f29e873 100644 --- a/Packages/viewerbase/client/components/viewer/toolbarSectionTools/toolbarSectionTools.styl +++ b/Packages/viewerbase/client/components/viewer/toolbarSectionTools/toolbarSectionTools.styl @@ -9,8 +9,9 @@ $distance = 12px bottom: - ($toolbarDrawerHeight + $distance) height: $toolbarDrawerHeight left: 0 + min-width: 100% position: absolute - width: 100% + white-space: nowrap .toolbarSectionDrawer background-color: $uiGrayDarker @@ -30,4 +31,4 @@ $distance = 12px width: 8px &.active .buttonLabel i - transform(rotateX(180deg)) \ No newline at end of file + transform(rotateX(180deg)) diff --git a/Packages/worklist/client/components/worklistPagination/worklistPagination.html b/Packages/worklist/client/components/worklistPagination/worklistPagination.html new file mode 100644 index 000000000..050e4ec6f --- /dev/null +++ b/Packages/worklist/client/components/worklistPagination/worklistPagination.html @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/Packages/worklist/client/components/worklistPagination/worklistPagination.js b/Packages/worklist/client/components/worklistPagination/worklistPagination.js new file mode 100644 index 000000000..4409a471b --- /dev/null +++ b/Packages/worklist/client/components/worklistPagination/worklistPagination.js @@ -0,0 +1,58 @@ +import { Template } from 'meteor/templating'; + +import './worklistPagination.html'; + +const visiblePages = 10; +Template.worklistPagination.onCreated(function() { + let instance = Template.instance(); + // replace parentVariable with the name of the instance variable + instance.parent = this.parent(1); +}); + +Template.worklistPagination.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.worklistPagination.helpers({ + recordCount() { + const instance = Template.instance(); + return instance.parent.recordCount.get(); + } +}); + +Template.worklistPagination.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)); + } +}); diff --git a/Packages/worklist/client/components/worklistPagination/worklistPagination.styl b/Packages/worklist/client/components/worklistPagination/worklistPagination.styl new file mode 100644 index 000000000..1cb183ff9 --- /dev/null +++ b/Packages/worklist/client/components/worklistPagination/worklistPagination.styl @@ -0,0 +1,39 @@ +@import "{design}/app" + +.worklistPagination + font-size: 13px + font-weight: normal !important + + .rows-per-page + padding-top: 20px + + label + font-weight: normal + + select + background-color: $primaryBackgroundColor + color: white + + .page-number + text-align: right + + label + font-weight: normal + + ul#pagination + + li + a + padding: 4px 8px + background-color: $primaryBackgroundColor + color: white + + &:hover + color: $activeColor + + .active + + a + background-color: $uiGrayDarker + color: white + diff --git a/Packages/worklist/client/components/worklistResult/worklistResult.html b/Packages/worklist/client/components/worklistResult/worklistResult.html index 3fe695877..b74813f4b 100644 --- a/Packages/worklist/client/components/worklistResult/worklistResult.html +++ b/Packages/worklist/client/components/worklistResult/worklistResult.html @@ -92,6 +92,10 @@ {{/each}} + + + {{> worklistPagination }} + {{#if session "showLoadingText"}} {{>loadingText}} {{ else }} diff --git a/Packages/worklist/client/components/worklistResult/worklistResult.js b/Packages/worklist/client/components/worklistResult/worklistResult.js index 5edb2c79c..828081518 100644 --- a/Packages/worklist/client/components/worklistResult/worklistResult.js +++ b/Packages/worklist/client/components/worklistResult/worklistResult.js @@ -6,19 +6,37 @@ Template.worklistResult.helpers({ * by Patient name and Study Date in Ascending order. */ studies() { - const sortOption = Session.get('sortOption'); - if (sortOption) { - return WorklistStudies.find({}, { - sort: sortOption - }); + 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'); } - return WorklistStudies.find({}, { - sort: { - patientName: 1, - studyDate: 1 - } - }); + // Pagination parameters + const rowsPerPage = instance.rowsPerPage.get(); + const currentPage = instance.currentPage.get(); + const offset = rowsPerPage * currentPage; + const limit = offset + rowsPerPage; + + studies = WorklistStudies.find({}, { + sort: sortOption + }).fetch(); + + if (!studies) { + return; + } + + // Update record count + instance.recordCount.set(studies.length); + + // Limit studies + return studies.slice(offset, limit); }, numberOfStudies() { @@ -163,6 +181,15 @@ Template.worklistResult.onCreated(() => { 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) { diff --git a/Packages/worklist/client/lib/jquery.twbsPagination/jquery.twbsPagination.min.js b/Packages/worklist/client/lib/jquery.twbsPagination/jquery.twbsPagination.min.js new file mode 100644 index 000000000..7cc71f9c4 --- /dev/null +++ b/Packages/worklist/client/lib/jquery.twbsPagination/jquery.twbsPagination.min.js @@ -0,0 +1,9 @@ +/* + * jQuery Bootstrap Pagination v1.3.1 + * https://github.com/esimakin/twbs-pagination + * + * Copyright 2014-2015 Eugene Simakin + * Released under Apache 2.0 license + * http://apache.org/licenses/LICENSE-2.0.html + */ +!function(a,b,c,d){"use strict";var e=a.fn.twbsPagination,f=function(c,d){if(this.$element=a(c),this.options=a.extend({},a.fn.twbsPagination.defaults,d),this.options.startPage<1||this.options.startPage>this.options.totalPages)throw new Error("Start page option is incorrect");if(this.options.totalPages=parseInt(this.options.totalPages),isNaN(this.options.totalPages))throw new Error("Total pages option is not correct!");if(this.options.visiblePages=parseInt(this.options.visiblePages),isNaN(this.options.visiblePages))throw new Error("Visible pages option is not correct!");if(this.options.totalPages"),this.$listContainer.addClass(this.options.paginationClass),"UL"!==g&&this.$element.append(this.$listContainer),this.render(this.getPages(this.options.startPage)),this.setupEvents(),this.options.initiateStartPageClick&&this.$element.trigger("page",this.options.startPage),this};f.prototype={constructor:f,destroy:function(){return this.$element.empty(),this.$element.removeData("twbs-pagination"),this.$element.off("page"),this},show:function(a){if(1>a||a>this.options.totalPages)throw new Error("Page is incorrect.");return this.render(this.getPages(a)),this.setupEvents(),this.$element.trigger("page",a),this},buildListItems:function(a){var b=[];if(this.options.first&&b.push(this.buildItem("first",1)),this.options.prev){var c=a.currentPage>1?a.currentPage-1:this.options.loop?this.options.totalPages:1;b.push(this.buildItem("prev",c))}for(var d=0;d"),e=a(""),f=null;switch(b){case"page":f=c,d.addClass(this.options.pageClass);break;case"first":f=this.options.first,d.addClass(this.options.firstClass);break;case"prev":f=this.options.prev,d.addClass(this.options.prevClass);break;case"next":f=this.options.next,d.addClass(this.options.nextClass);break;case"last":f=this.options.last,d.addClass(this.options.lastClass)}return d.data("page",c),d.data("page-type",b),d.append(e.attr("href",this.makeHref(c)).html(f)),d},getPages:function(a){var b=[],c=Math.floor(this.options.visiblePages/2),d=a-c+1-this.options.visiblePages%2,e=a+c;0>=d&&(d=1,e=this.options.visiblePages),e>this.options.totalPages&&(d=this.options.totalPages-this.options.visiblePages+1,e=this.options.totalPages);for(var f=d;e>=f;)b.push(f),f++;return{currentPage:a,numeric:b}},render:function(b){var c=this;this.$listContainer.children().remove(),this.$listContainer.append(this.buildListItems(b)),this.$listContainer.children().each(function(){var d=a(this),e=d.data("page-type");switch(e){case"page":d.data("page")===b.currentPage&&d.addClass(c.options.activeClass);break;case"first":d.toggleClass(c.options.disabledClass,1===b.currentPage);break;case"last":d.toggleClass(c.options.disabledClass,b.currentPage===c.options.totalPages);break;case"prev":d.toggleClass(c.options.disabledClass,!c.options.loop&&1===b.currentPage);break;case"next":d.toggleClass(c.options.disabledClass,!c.options.loop&&b.currentPage===c.options.totalPages)}})},setupEvents:function(){var b=this;this.$listContainer.find("li").each(function(){var c=a(this);return c.off(),c.hasClass(b.options.disabledClass)||c.hasClass(b.options.activeClass)?void c.on("click",!1):void c.click(function(a){!b.options.href&&a.preventDefault(),b.show(parseInt(c.data("page")))})})},makeHref:function(a){return this.options.href?this.options.href.replace(this.options.hrefVariable,a):"#"}},a.fn.twbsPagination=function(b){var c,e=Array.prototype.slice.call(arguments,1),g=a(this),h=g.data("twbs-pagination"),i="object"==typeof b&&b;return h||g.data("twbs-pagination",h=new f(this,i)),"string"==typeof b&&(c=h[b].apply(h,e)),c===d?g:c},a.fn.twbsPagination.defaults={totalPages:0,startPage:1,visiblePages:5,initiateStartPageClick:!0,href:!1,hrefVariable:"{{number}}",first:"First",prev:"Previous",next:"Next",last:"Last",loop:!1,onPageClick:null,paginationClass:"pagination",nextClass:"next",prevClass:"prev",lastClass:"last",firstClass:"first",pageClass:"page",activeClass:"active",disabledClass:"disabled"},a.fn.twbsPagination.Constructor=f,a.fn.twbsPagination.noConflict=function(){return a.fn.twbsPagination=e,this}}(window.jQuery,window,document); \ No newline at end of file diff --git a/Packages/worklist/package.js b/Packages/worklist/package.js index 520b5106e..214f38c0d 100644 --- a/Packages/worklist/package.js +++ b/Packages/worklist/package.js @@ -69,6 +69,10 @@ Package.onUse(function (api) { 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/worklistPagination/worklistPagination.html', 'client'); + api.addFiles('client/components/worklistPagination/worklistPagination.styl', 'client'); + api.addFiles('client/components/worklistPagination/worklistPagination.js', 'client'); // Client-side library functions api.addFiles('client/lib/getStudyMetadata.js', 'client'); @@ -79,6 +83,7 @@ Package.onUse(function (api) { api.addFiles('client/lib/worklist.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');