From 9e69c43dda372daf615da35e44f8f43a042d7c84 Mon Sep 17 00:00:00 2001 From: "Emanuel F. Oliveira" Date: Mon, 16 Jan 2017 15:30:54 -0200 Subject: [PATCH] Most JS files, all helpers and simple reconciliations --- .../toolbarSection/toolbarSection.js | 7 +- .../client/components/viewer/viewer.js | 19 +- .../components/stageSortable/stageSortable.js | 73 ++-- .../textEntryDialog/textEntryDialog.js | 44 +-- .../client/compatibility/dialogPolyfill.js | 4 +- .../imageThumbnail/imageThumbnail.js | 12 +- .../thumbnailEntry/thumbnailEntry.js | 213 +----------- .../annotationDialogs/annotationDialogs.js | 115 ------- .../viewer/cineDialog/cineDialog.js | 50 +-- .../confirmDeleteDialog.js | 78 +---- .../displaySetNavigation.js | 4 +- .../viewer/imageControls/imageControls.js | 9 +- .../viewer/layoutButton/layoutButton.js | 6 +- .../viewer/layoutChooser/layoutChooser.js | 18 +- .../loadingIndicator/loadingIndicator.js | 21 +- .../viewer/playClipButton/playClipButton.js | 7 +- .../studySeriesQuickSwitch.js | 6 +- .../studyTimepointBrowser.js | 4 +- .../studyTimepointStudy.js | 8 +- .../toolbarSectionButton.js | 1 + .../viewer/viewerMain/viewerMain.js | 46 ++- .../viewer/viewportOverlay/viewportOverlay.js | 312 ++++++------------ Packages/ohif-viewerbase/client/index.js | 3 +- .../ohif-viewerbase/client/lib/WLPresets.js | 92 +++++- .../client/lib/createStacks.js | 69 ++-- .../client/lib/displayReferenceLines.js | 21 +- .../ohif-viewerbase/client/lib/getImageId.js | 9 +- .../client/lib/getWADORSImageId.js | 2 +- .../lib/helpers/capitalizeFirstLetter.js | 6 +- .../client/lib/helpers/formatDA.js | 12 +- .../client/lib/helpers/formatJSDate.js | 5 +- .../lib/helpers/formatNumberPrecision.js | 6 +- .../client/lib/helpers/formatPN.js | 7 +- .../client/lib/helpers/formatTM.js | 12 +- .../client/lib/helpers/getUsername.js | 5 +- .../client/lib/helpers/ifTypeIs.js | 4 +- .../ohif-viewerbase/client/lib/helpers/inc.js | 4 +- .../client/lib/helpers/isDisplaySetActive.js | 26 +- .../client/lib/helpers/isTouchDevice.js | 8 +- .../client/lib/helpers/jsDateFromNow.js | 6 +- .../client/lib/helpers/objectEach.js | 5 +- .../client/lib/helpers/objectToPairs.js | 5 +- .../lib/helpers/prettyPrintStringify.js | 4 +- .../client/lib/helpers/sorting.js | 4 +- .../client/lib/helpers/studyThumbnails.js | 3 + .../lib/instanceClassSpecificViewport.js | 14 +- .../ohif-viewerbase/client/lib/isImage.js | 102 +++--- .../client/lib/metaDataProvider.js | 51 +-- .../client/lib/setActiveViewport.js | 36 +- .../client/lib/setFocusToActiveViewport.js | 17 +- .../lib/setMammogramViewportAlignment.js | 29 +- .../client/lib/sopClassDictionary.js | 5 +- .../ohif-viewerbase/client/lib/sortStudy.js | 20 +- .../client/lib/switchToImageByIndex.js | 8 +- .../client/lib/switchToImageRelative.js | 8 +- .../client/lib/updateAllViewports.js | 6 +- .../client/lib/viewportUtils.js | 26 +- Packages/ohif-viewerbase/package.js | 104 +----- 58 files changed, 776 insertions(+), 1025 deletions(-) diff --git a/LesionTracker/client/components/toolbarSection/toolbarSection.js b/LesionTracker/client/components/toolbarSection/toolbarSection.js index b9abdc9c6..836c90290 100644 --- a/LesionTracker/client/components/toolbarSection/toolbarSection.js +++ b/LesionTracker/client/components/toolbarSection/toolbarSection.js @@ -1,5 +1,6 @@ import { Template } from 'meteor/templating'; import { Session } from 'meteor/session'; +import { Viewerbase } from 'meteor/ohif:viewerbase'; Template.toolbarSection.helpers({ // Returns true if the view shall be split in two viewports @@ -141,7 +142,7 @@ Template.toolbarSection.helpers({ title: 'CINE', classes: 'imageViewerCommand', svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-cineplay-toggle', - disableFunction: hasMultipleFrames + disableFunction: Viewerbase.viewportUtils.hasMultipleFrames }); const buttonData = []; @@ -168,11 +169,11 @@ Template.toolbarSection.helpers({ }); buttonData.push({ - id: 'link', + id: 'linkStackScroll', title: 'Link', classes: 'imageViewerCommand toolbarSectionButton nonAutoDisableState', svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-link', - disableFunction: isStackScrollLinkingDisabled + disableFunction: Viewerbase.viewportUtils.isStackScrollLinkingDisabled }); buttonData.push({ diff --git a/LesionTracker/client/components/viewer/viewer.js b/LesionTracker/client/components/viewer/viewer.js index efe50c700..32d908ee3 100644 --- a/LesionTracker/client/components/viewer/viewer.js +++ b/LesionTracker/client/components/viewer/viewer.js @@ -2,6 +2,7 @@ import { Template } from 'meteor/templating'; import { Session } from 'meteor/session'; import { ReactiveDict } from 'meteor/reactive-dict'; import { OHIF } from 'meteor/ohif:core'; +import 'meteor/ohif:viewerbase'; import { _ } from 'meteor/underscore'; Session.set('ViewerMainReady', false); @@ -32,15 +33,15 @@ Template.viewer.onCreated(() => { toolManager.setActiveTool('nonTarget'); }, // Viewport functions - toggleCineDialog, - clearTools, - resetViewport, - invert, - flipV, - flipH, - rotateL, - rotateR, - link + toggleCineDialog: OHIF.viewerbase.viewportUtils.toggleCineDialog, + clearTools: OHIF.viewerbase.viewportUtils.clearTools, + resetViewport: OHIF.viewerbase.viewportUtils.resetViewport, + invert: OHIF.viewerbase.viewportUtils.invert, + flipV: OHIF.viewerbase.viewportUtils.flipV, + flipH: OHIF.viewerbase.viewportUtils.flipH, + rotateL: OHIF.viewerbase.viewportUtils.rotateL, + rotateR: OHIF.viewerbase.viewportUtils.rotateR, + linkStackScroll: OHIF.viewerbase.viewportUtils.linkStackScroll }); if (ViewerData[contentId].loadedSeriesData) { diff --git a/Packages/ohif-hanging-protocols/client/components/stageSortable/stageSortable.js b/Packages/ohif-hanging-protocols/client/components/stageSortable/stageSortable.js index e04007898..6caa9ac5e 100644 --- a/Packages/ohif-hanging-protocols/client/components/stageSortable/stageSortable.js +++ b/Packages/ohif-hanging-protocols/client/components/stageSortable/stageSortable.js @@ -1,9 +1,16 @@ +import { Template } from 'meteor/templating'; +import { Session } from 'meteor/session'; +import { Random } from 'meteor/random'; + +import { OHIF } from 'meteor/ohif:core'; +import 'meteor/ohif:viewerbase'; + /** * Extend the Array prototype with a Swap function * so we can swap stages more easily */ Array.prototype.move = function(oldIndex, newIndex) { - var value = this[oldIndex]; + const value = this[oldIndex]; newIndex = Math.max(0, newIndex); newIndex = Math.min(this.length, newIndex); @@ -22,13 +29,13 @@ Array.prototype.move = function(oldIndex, newIndex) { * @returns {number} The index of the specified stage within the Protocol, * or undefined if it is not present. */ -function getStageIndex(protocol, id) { - var stageIndex; +const getStageIndex = (protocol, id) => { + let stageIndex; if (!protocol || !protocol.stages) { return; } - protocol.stages.forEach(function(stage, index) { + protocol.stages.forEach((stage, index) => { if (stage.id === id) { stageIndex = index; return false; @@ -36,7 +43,7 @@ function getStageIndex(protocol, id) { }); return stageIndex; -} +}; Template.stageSortable.helpers({ /** @@ -44,7 +51,7 @@ Template.stageSortable.helpers({ * * @returns {boolean} Whether or not the stage is currently being displayed */ - isActiveStage: function() { + isActiveStage() { // Rerun this function every time the layout manager has been updated Session.get('LayoutManagerUpdated'); @@ -53,7 +60,7 @@ Template.stageSortable.helpers({ return; } - var currentStage = ProtocolEngine.getCurrentStageModel(); + const currentStage = ProtocolEngine.getCurrentStageModel(); if (!currentStage) { return false; } @@ -66,8 +73,8 @@ Template.stageSortable.helpers({ * * @returns {number|*} */ - stageLabel: function() { - var stage = this; + stageLabel() { + const stage = this; // If no Protocol Engine has been defined yet, stop here to prevent errors if (!ProtocolEngine) { @@ -75,10 +82,10 @@ Template.stageSortable.helpers({ } // Retrieve the last saved copy of the current protocol - var lastSavedCopy = HP.ProtocolStore.getProtocol(ProtocolEngine.protocol.id); + const lastSavedCopy = HangingProtocols.findOne(ProtocolEngine.protocol._id); // Try to find the index of this stage in the previously saved copy - var stageIndex = getStageIndex(lastSavedCopy, stage.id); + let stageIndex = getStageIndex(lastSavedCopy, stage.id); // If the stage is new, and therefore wasn't present in the last save, // retrieve it's index in the array of new stage ids and use that for @@ -88,11 +95,11 @@ Template.stageSortable.helpers({ Session.get('timeAgoVariable'); // Find the index of the stage in the array of newly created stage IDs - var newStageNumber = ProtocolEngine.newStageIds.indexOf(stage.id) + 1; + const newStageNumber = ProtocolEngine.newStageIds.indexOf(stage.id) + 1; // Use Moment.js to format the createdDate of this stage relative to the // current time - var dateCreatedFromNow = moment(stage.createdDate).fromNow(); + const dateCreatedFromNow = moment(stage.createdDate).fromNow(); // Return the label for the new stage, // e.g. "New Stage 1 (created a few seconds ago)" @@ -108,7 +115,7 @@ Template.stageSortable.helpers({ * * @returns {boolean} Whether or not a later stage exists */ - isNextAvailable: function() { + isNextAvailable() { // Run this helper whenever the ProtocolEngine / LayoutManager has changed Session.get('LayoutManagerUpdated'); @@ -125,7 +132,7 @@ Template.stageSortable.helpers({ * * @returns {boolean} Whether or not an earlier stage exists */ - isPreviousAvailable: function() { + isPreviousAvailable() { // Run this helper whenever the ProtocolEngine / LayoutManager has changed Session.get('LayoutManagerUpdated'); @@ -143,9 +150,9 @@ Template.stageSortable.events({ /** * Displays a stage when its title is clicked */ - 'click .sortable-item span': function() { + 'click .sortable-item span'() { // Retrieve the index of this stage in the display set sequences - var stageIndex = getStageIndex(ProtocolEngine.protocol, this.id); + const stageIndex = getStageIndex(ProtocolEngine.protocol, this.id); // Display the selected stage ProtocolEngine.setCurrentProtocolStage(stageIndex); @@ -154,12 +161,12 @@ Template.stageSortable.events({ * Creates a new stage and adds it to the currently loaded Protocol at * the end of the display set sequence */ - 'click #addStage': function() { + 'click #addStage'() { // Retrieve the model describing the current stage - var stage = ProtocolEngine.getCurrentStageModel(); + const stage = ProtocolEngine.getCurrentStageModel(); // Clone this stage to create a new stage - var newStage = stage.createClone(); + const newStage = stage.createClone(); // Remove the stage's name if it has one delete newStage.name; @@ -171,7 +178,7 @@ Template.stageSortable.events({ ProtocolEngine.newStageIds.push(newStage.id); // Calculate the index of the last stage in the display set sequence - var stageIndex = ProtocolEngine.protocol.stages.length - 1; + const stageIndex = ProtocolEngine.protocol.stages.length - 1; // Switch to the last stage in the display set sequence ProtocolEngine.setCurrentProtocolStage(stageIndex); @@ -181,22 +188,22 @@ Template.stageSortable.events({ * the stages array. If it is the currently active stage, the current stage is * set to one stage earlier in the display set sequence. */ - 'click .deleteStage': function() { + 'click .deleteStage'() { // If this is the only stage in the Protocol, stop here if (ProtocolEngine.protocol.stages.length === 1) { return; } - var stageId = this.id; + const stageId = this.id; - var options = { + const options = { title: 'Remove Protocol Stage', text: 'Are you sure you would like to remove this Protocol Stage? This cannot be reversed.' }; - showConfirmDialog(function() { + OHIF.viewerbase.showConfirmDialog(() => { // Retrieve the index of this stage in the display set sequences - var stageIndex = getStageIndex(ProtocolEngine.protocol, stageId); + const stageIndex = getStageIndex(ProtocolEngine.protocol, stageId); // Remove it from the display set sequence ProtocolEngine.protocol.stages.splice(stageIndex, 1); @@ -204,7 +211,7 @@ Template.stageSortable.events({ // If we have removed the currently active stage, switch to the one before it if (ProtocolEngine.stage === stageIndex) { // Make sure we don't try to switch to a stage index below zero - var newStageIndex = Math.max(stageIndex - 1, 0); + const newStageIndex = Math.max(stageIndex - 1, 0); // Display the new stage ProtocolEngine.setCurrentProtocolStage(newStageIndex); @@ -215,10 +222,10 @@ Template.stageSortable.events({ }, options); }, - 'click .moveStageUp': function() { + 'click .moveStageUp'() { // Get the old and new indices following a 'sort' event - var oldIndex = ProtocolEngine.stage; - var newIndex = Math.max(ProtocolEngine.stage - 1, 0); + const oldIndex = ProtocolEngine.stage; + const newIndex = Math.max(ProtocolEngine.stage - 1, 0); if (oldIndex === newIndex) { return; @@ -235,10 +242,10 @@ Template.stageSortable.events({ // Update the Session variable to the UI re-renders Session.set('LayoutManagerUpdated', Random.id()); }, - 'click .moveStageDown': function() { + 'click .moveStageDown'() { // Get the old and new indices following a 'sort' event - var oldIndex = ProtocolEngine.stage; - var newIndex = Math.min(ProtocolEngine.stage + 1, ProtocolEngine.protocol.stages.length - 1); + const oldIndex = ProtocolEngine.stage; + const newIndex = Math.min(ProtocolEngine.stage + 1, ProtocolEngine.protocol.stages.length - 1); if (oldIndex === newIndex) { return; diff --git a/Packages/ohif-hanging-protocols/client/components/textEntryDialog/textEntryDialog.js b/Packages/ohif-hanging-protocols/client/components/textEntryDialog/textEntryDialog.js index 7e3edff13..a36251feb 100644 --- a/Packages/ohif-hanging-protocols/client/components/textEntryDialog/textEntryDialog.js +++ b/Packages/ohif-hanging-protocols/client/components/textEntryDialog/textEntryDialog.js @@ -1,4 +1,10 @@ -var keys = { +import { Blaze } from 'meteor/blaze'; +import { $ } from 'meteor/jquery'; +import { Template } from 'meteor/templating'; + +import { Viewerbase } from 'meteor/ohif:viewerbase'; + +const keys = { ESC: 27, ENTER: 13 }; @@ -9,7 +15,7 @@ var keys = { * * @param dialog The DOM element of the dialog to close */ -function closeHandler(dialog) { +const closeHandler = dialog => { // Hide the lesion dialog $(dialog).css('display', 'none'); @@ -17,8 +23,8 @@ function closeHandler(dialog) { $('.removableBackdrop').remove(); // Restore the focus to the active viewport - setFocusToActiveViewport(); -} + Viewerbase.setFocusToActiveViewport(); +}; /** * Displays and updates the UI of the Text Entry Dialog given a new title, @@ -30,10 +36,10 @@ function closeHandler(dialog) { */ openTextEntryDialog = function(title, instructions, currentValue, doneCallback) { // Get the lesion location dialog - var dialog = $('.textEntryDialog'); + const dialog = $('.textEntryDialog'); // Clear any input that is still on the page - var currentValueInput = dialog.find('input.currentValue'); + const currentValueInput = dialog.find('input.currentValue'); currentValueInput.val(currentValue); // Store the Dialog DOM data, rule level and rule in the template data @@ -49,10 +55,10 @@ openTextEntryDialog = function(title, instructions, currentValue, doneCallback) dialog.css('display', 'block'); // Show the backdrop - UI.render(Template.removableBackdrop, document.body); + Blaze.render(Template.removableBackdrop, document.body); // Make sure the context menu is closed when the user clicks away - $('.removableBackdrop').one('mousedown touchstart', function() { + $('.removableBackdrop').one('mousedown touchstart', () => { closeHandler(dialog); }); }; @@ -68,17 +74,17 @@ Template.textEntryDialog.events({ * Save the user-specified text * */ - 'click .save': function() { + 'click .save'() { // Retrieve the input properties to the template - var dialog = Template.textEntryDialog.dialog; - var currentValue = dialog.find('input.currentValue').val(); + const dialog = Template.textEntryDialog.dialog; + const currentValue = dialog.find('input.currentValue').val(); // If currentValue input is undefined, prevent saving this rule if (currentValue === undefined) { return; } - var doneCallback = Template.textEntryDialog.doneCallback; + const doneCallback = Template.textEntryDialog.doneCallback; if (doneCallback) { doneCallback(currentValue); } @@ -89,7 +95,7 @@ Template.textEntryDialog.events({ /** * Allow the user to click the Cancel button to close the dialog */ - 'click .cancel': function() { + 'click .cancel'() { closeHandler(Template.textEntryDialog.dialog); }, /** @@ -98,22 +104,22 @@ Template.textEntryDialog.events({ * @param event The Keydown event details * @returns {boolean} Return false to prevent bubbling of the event */ - 'keydown .textEntryDialog': function(event) { - var dialog = Template.textEntryDialog.dialog; + 'keydown .textEntryDialog'(event) { + const dialog = Template.textEntryDialog.dialog; // If Esc key is pressed, close the dialog if (event.which === keys.ESC) { closeHandler(dialog); return false; } else if (event.which === keys.ENTER) { - var currentValue = dialog.find('input.currentValue').val(); + const currentValue = dialog.find('input.currentValue').val(); // If currentValue input is undefined, prevent saving this rule if (currentValue === undefined) { return; } - var doneCallback = Template.textEntryDialog.doneCallback; + const doneCallback = Template.textEntryDialog.doneCallback; if (doneCallback) { doneCallback(currentValue); } @@ -128,9 +134,9 @@ Template.textEntryDialog.events({ * @param event The Change event for the input * @param template The current template context */ - 'change input.currentValue': function(event, template) { + 'change input.currentValue'(event, template) { // Get the DOM element representing the input box - var input = $(event.currentTarget); + const input = $(event.currentTarget); // Update the template data with the current value Template.textEntryDialog.currentValue = input.val(); diff --git a/Packages/ohif-viewerbase/client/compatibility/dialogPolyfill.js b/Packages/ohif-viewerbase/client/compatibility/dialogPolyfill.js index 77a4ceea6..a7b88abf3 100644 --- a/Packages/ohif-viewerbase/client/compatibility/dialogPolyfill.js +++ b/Packages/ohif-viewerbase/client/compatibility/dialogPolyfill.js @@ -189,8 +189,8 @@ var dialogPolyfill = (function() { dialogPolyfill.registerDialog = function(element) { if (element.show) { - console.warn("This browser already supports , the polyfill " + - "may not work correctly."); + // console.warn("This browser already supports , the polyfill " + + // "may not work correctly."); } element.show = dialogPolyfill.showDialog.bind(element, false); element.showModal = dialogPolyfill.showDialog.bind(element, true); diff --git a/Packages/ohif-viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.js b/Packages/ohif-viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.js index a490d7c5f..d1731dd38 100644 --- a/Packages/ohif-viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.js +++ b/Packages/ohif-viewerbase/client/components/studyBrowser/imageThumbnail/imageThumbnail.js @@ -1,6 +1,10 @@ import { Template } from 'meteor/templating'; import { Tracker } from 'meteor/tracker'; import { Session } from 'meteor/session'; +// OHIF Modules +import { OHIF } from 'meteor/ohif:core'; +// Local Modules +import { getImageId } from '../../../lib/getImageId.js'; Template.imageThumbnail.onCreated(() => { const instance = Template.instance(); @@ -26,6 +30,10 @@ Template.imageThumbnail.onRendered(() => { const element = $element.get(0); instance.refreshImage = () => { + if (!element) { + return; + } + // Disable cornerstone for thumbnail element and remove its canvas cornerstone.disable(element); @@ -75,7 +83,9 @@ Template.imageThumbnail.onDestroyed(() => { const $element = $parent.find('.imageThumbnailCanvas'); const element = $element.get(0); - cornerstone.disable(element); + if (element) { + cornerstone.disable(element); + } }); Template.imageThumbnail.helpers({ diff --git a/Packages/ohif-viewerbase/client/components/studyBrowser/thumbnailEntry/thumbnailEntry.js b/Packages/ohif-viewerbase/client/components/studyBrowser/thumbnailEntry/thumbnailEntry.js index ab4217f97..a24674fd0 100644 --- a/Packages/ohif-viewerbase/client/components/studyBrowser/thumbnailEntry/thumbnailEntry.js +++ b/Packages/ohif-viewerbase/client/components/studyBrowser/thumbnailEntry/thumbnailEntry.js @@ -1,202 +1,9 @@ -const cloneElement = (element, targetId) => { - // Clone the DOM element - const clone = element.cloneNode(true); +import { Template } from 'meteor/templating'; +import { _ } from 'meteor/underscore'; +import { Session } from 'meteor/session'; - // Find any canvas children to clone - const clonedCanvases = $(clone).find('canvas'); - clonedCanvases.each((canvasIndex, clonedCanvas) => { - // Draw from the original canvas to the cloned canvas - const context = clonedCanvas.getContext('2d'); - const thumbnailCanvas = $(element).find('canvas').get(canvasIndex); - context.drawImage(thumbnailCanvas, 0, 0); - }); - - // Update the clone with the targetId - clone.id = targetId; - clone.style.visibility = 'hidden'; - return clone; -}; - -const thumbnailDragStartHandler = (event, data) => { - // Prevent any scrolling behaviour normally caused by the original event - event.originalEvent.preventDefault(); - - // Identify the current study and series index from the thumbnail's DOM position - const targetThumbnail = event.currentTarget; - const $imageThumbnail = $(targetThumbnail); - - // Clone the image thumbnail - const targetId = 'DragClone'; - const clone = cloneElement(targetThumbnail, targetId); - const $clone = $(clone); - $clone.addClass('imageThumbnailClone'); - - // Set pointerEvents to pass through the clone DOM element - // This is necessary in order to identify what is below it - // when using document.elementFromPoint - clone.style.pointerEvents = 'none'; - - // Append the clone to the body - document.body.appendChild(clone); - - // Set the cursor x and y positions from the current touch/mouse coordinates - let cursorX, cursorY; - // Handle touchStart cases - if (event.type === 'touchstart') { - cursorX = event.originalEvent.touches[0].pageX; - cursorY = event.originalEvent.touches[0].pageY; - } else { - cursorX = event.pageX; - cursorY = event.pageY; - - // Also hook up event handlers for mouse events - const handlers = {}; - handlers.mousemove = event => thumbnailDragHandler(event); - handlers.mouseup = event => thumbnailDragEndHandler(event, data, handlers); - $(document).on('mousemove', handlers.mousemove); - $(document).on('mouseup', handlers.mouseup); - } - - // This block gets the current offset of the touch/mouse - // relative to the window - // - // i.e. Where did the user grab it from? - const offset = $imageThumbnail.offset(); - const left = offset.left; - const top = offset.top; - - // This difference is saved for later so the element movement looks normal - const diff = { - x: cursorX - left, - y: cursorY - top - }; - $clone.data('diff', diff); - - // This sets the default style properties of the cloned element so it is - // ready to be dragged around the page - $clone.css({ - left: cursorX - diff.x, - position: 'fixed', - top: cursorY - diff.y, - visibility: 'hidden', - 'z-index': 100000 - }); -}; - -const thumbnailDragHandler = event => { - // Get the touch/mouse coordinates from the event - let cursorX, cursorY; - if (event.type === 'touchmove') { - cursorX = event.originalEvent.changedTouches[0].pageX; - cursorY = event.originalEvent.changedTouches[0].pageY; - } else { - cursorX = event.pageX; - cursorY = event.pageY; - } - - // Find the clone element and update it's position on the page - const $clone = $('#DragClone'); - const diff = $clone.data('diff'); - $clone.css({ - left: cursorX - diff.x, - position: 'fixed', - top: cursorY - diff.y, - visibility: 'visible', - 'z-index': 100000 - }); - - // Identify the element below the current cursor position - const elemBelow = document.elementFromPoint(cursorX, cursorY); - - // If none exists, stop here - if (!elemBelow) { - return; - } - - // Remove any current faded effects on viewports - $('.imageViewerViewport canvas').removeClass('faded'); - - // Figure out what to do depending on what we're dragging over - const $viewportsDraggedOver = $(elemBelow).parents('.imageViewerViewport'); - if ($viewportsDraggedOver.length) { - // If we're dragging over a non-empty viewport, fade it and change the cursor style - $viewportsDraggedOver.find('canvas').not('.magnifyTool').addClass('faded'); - document.body.style.cursor = 'copy'; - } else if (elemBelow.classList.contains('imageViewerViewport') && elemBelow.classList.contains('empty')) { - // If we're dragging over an empty viewport, just change the cursor style - document.body.style.cursor = 'copy'; - } else { - // Otherwise, keep the cursor as no-drop style - document.body.style.cursor = 'no-drop'; - } -}; - -const thumbnailDragEndHandler = (event, data, handlers) => { - // Remove the mouse event listeners - if (handlers) { - $(document).off('mousemove', handlers.mousemove); - $(document).off('mouseup', handlers.mouseup); - } - - // Reset the cursor style to the default - document.body.style.cursor = 'auto'; - - // Get the cloned element - const $clone = $('#DragClone'); - - // If it doesn't exist, stop here - if (!$clone.length) { - return; - } - - const top = $clone.offset().top; - const left = $clone.offset().left; - const diff = $clone.data('diff'); - - // Identify the element below the cloned element position - const elemBelow = document.elementFromPoint(left + diff.x, top + diff.y); - - // Remove all cloned elements from the page - $('.imageThumbnailClone').remove(); - - // Remove any current faded effects on viewports - $('.imageViewerViewport canvas').removeClass('faded'); - - // If none exists, stop here - if (!elemBelow) { - return; - } - - // Remove any fade effects on the element below - elemBelow.classList.remove('faded'); - - let element; - const $viewportsDraggedOver = $(elemBelow).closest('.imageViewerViewport'); - if ($viewportsDraggedOver.length) { - // If we're dragging over a non-empty viewport, retrieve it - element = $viewportsDraggedOver.get(0); - } else if (elemBelow.classList.contains('imageViewerViewport') && - elemBelow.classList.contains('empty')) { - // If we're dragging over an empty viewport, retrieve that instead - element = elemBelow; - } else { - // Otherwise, stop here - return false; - } - - // If there is no stored drag and drop data, stop here - if (!data) { - return false; - } - - // Get the dropped viewport index - const viewportIndex = $('.imageViewerViewport').index(element); - - // Rerender the viewport using the dragged thumbnail data - window.layoutManager.rerenderViewportWithNewDisplaySet(viewportIndex, data); - - return false; -}; +import { OHIF } from 'meteor/ohif:core'; +import { thumbnailDragHandlers } from '../../../lib/thumbnailDragHandlers'; Template.thumbnailEntry.onCreated(() => { const instance = Template.instance(); @@ -209,16 +16,16 @@ Template.thumbnailEntry.events({ // Event handlers for drag and drop 'touchstart .thumbnailEntry, mousedown .thumbnailEntry'(event, instance) { const data = instance.data.thumbnail.stack; - instance.isDragAndDrop && thumbnailDragStartHandler(event, data); + instance.isDragAndDrop && thumbnailDragHandlers.thumbnailDragStartHandler(event, data); }, 'touchmove .thumbnailEntry'(event, instance) { - instance.isDragAndDrop && thumbnailDragHandler(event); + instance.isDragAndDrop && thumbnailDragHandlers.thumbnailDragHandler(event); }, 'touchend .thumbnailEntry'(event, instance) { const data = instance.data.thumbnail.stack; - instance.isDragAndDrop && thumbnailDragEndHandler(event, data); + instance.isDragAndDrop && thumbnailDragHandlers.thumbnailDragEndHandler(event, data); }, // Event handlers for click (quick switch) @@ -228,7 +35,7 @@ Template.thumbnailEntry.events({ const data = instance.data.thumbnail.stack; // Rerender the viewport using the clicked thumbnail data - window.layoutManager.rerenderViewportWithNewDisplaySet(instance.data.viewportIndex, data); + OHIF.viewerbase.layoutManager.rerenderViewportWithNewDisplaySet(instance.data.viewportIndex, data); } }, @@ -241,7 +48,7 @@ Template.thumbnailEntry.events({ const data = instance.data.thumbnail.stack; // Rerender the viewport using the clicked thumbnail data - window.layoutManager.rerenderViewportWithNewDisplaySet(viewportIndex, data); + OHIF.viewerbase.layoutManager.rerenderViewportWithNewDisplaySet(viewportIndex, data); } }); diff --git a/Packages/ohif-viewerbase/client/components/viewer/annotationDialogs/annotationDialogs.js b/Packages/ohif-viewerbase/client/components/viewer/annotationDialogs/annotationDialogs.js index 30c6ec1a0..e5e3e5a77 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/annotationDialogs/annotationDialogs.js +++ b/Packages/ohif-viewerbase/client/components/viewer/annotationDialogs/annotationDialogs.js @@ -1,118 +1,3 @@ -// ------ Tool configuration functions ----- // -getAnnotationTextCallback = function(doneChangingTextCallback) { - // This handles the text entry for the annotation tool - function keyPressHandler(e) { - // If Enter or Esc are pressed, close the dialog - if (e.which === 13 || e.which === 27) { - closeHandler(); - } - } - - function closeHandler() { - dialog.get(0).close(); - doneChangingTextCallback(getTextInput.val()); - // Reset the text value - getTextInput.val(''); - - // Reset the focus to the active viewport element - // This makes the mobile Safari keyboard close - const element = getActiveViewportElement(); - $(element).focus(); - } - - const dialog = $('#annotationDialog'); - if (dialog.get(0).open === true) { - return; - } - - const getTextInput = $('.annotationTextInput'); - - // Focus on the text input to open the Safari keyboard - getTextInput.focus(); - - dialog.get(0).showModal(); - - const confirm = dialog.find('.annotationDialogConfirm'); - confirm.off('click'); - confirm.on('click', () => { - closeHandler(); - }); - - // Use keydown since keypress doesn't handle ESC in Chrome - dialog.off('keydown'); - dialog.on('keydown', keyPressHandler); -}; - -changeAnnotationTextCallback = function(data, eventData, doneChangingTextCallback) { - const dialog = $('#relabelAnnotationDialog'); - if (dialog.get(0).open === true) { - return; - } - - if (isTouchDevice()) { - // Center the dialog on screen on touch devices - dialog.css({ - top: 0, - left: 0, - right: 0, - bottom: 0, - margin: 'auto' - }); - } else { - // Place the dialog above the tool that is being relabelled - dialog.css({ - top: eventData.currentPoints.page.y - dialog.outerHeight() - 20, - left: eventData.currentPoints.page.x - dialog.outerWidth() / 2 - }); - } - - const getTextInput = dialog.find('.annotationTextInput'); - const confirm = dialog.find('.relabelConfirm'); - const remove = dialog.find('.relabelRemove'); - - getTextInput.val(data.text); - - // Focus on the text input to open the Safari keyboard - getTextInput.focus(); - - dialog.get(0).showModal(); - - confirm.off('click'); - confirm.on('click', function() { - dialog.get(0).close(); - doneChangingTextCallback(data, getTextInput.val()); - }); - - // If the remove button is clicked, delete this marker - remove.off('click'); - remove.on('click', function() { - dialog.get(0).close(); - doneChangingTextCallback(data, undefined, true); - }); - - dialog.off('keydown'); - dialog.on('keydown', keyPressHandler); - - function keyPressHandler(e) { - // If Enter is pressed, close the dialog - if (e.which === 13) { - closeHandler(); - } - } - - function closeHandler() { - dialog.get(0).close(); - doneChangingTextCallback(data, getTextInput.val()); - // Reset the text value - getTextInput.val(''); - - // Reset the focus to the active viewport element - // This makes the mobile Safari keyboard close - const element = getActiveViewportElement(); - $(element).focus(); - } -}; - Template.annotationDialogs.onRendered(() => { const instance = Template.instance(); const dialogIds = ['annotationDialog', 'relabelAnnotationDialog']; diff --git a/Packages/ohif-viewerbase/client/components/viewer/cineDialog/cineDialog.js b/Packages/ohif-viewerbase/client/components/viewer/cineDialog/cineDialog.js index ec09a2dcf..113b9cf44 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/cineDialog/cineDialog.js +++ b/Packages/ohif-viewerbase/client/components/viewer/cineDialog/cineDialog.js @@ -1,9 +1,12 @@ import { Template } from 'meteor/templating'; import { SimpleSchema } from 'meteor/aldeed:simple-schema'; -import { OHIF } from 'meteor/ohif:core'; import { Session } from 'meteor/session'; import { _ } from 'meteor/underscore'; import { $ } from 'meteor/jquery'; +import { OHIF } from 'meteor/ohif:core'; +import { viewportUtils } from '../../../lib/viewportUtils'; +import { switchToImageRelative } from '../../../lib/switchToImageRelative'; +import { switchToImageByIndex } from '../../../lib/switchToImageByIndex'; Template.cineDialog.onCreated(() => { const instance = Template.instance(); @@ -34,18 +37,15 @@ Template.cineDialog.onCreated(() => { OHIF.viewer.cine.framesPerSecond = rate; // Update playClip toolData for this imageId - const element = getActiveViewportElement(); + const element = viewportUtils.getActiveViewportElement(); if (!element) { return; } - const playClipToolData = cornerstoneTools.getToolState(element, 'playClip'); - playClipToolData.data[0].framesPerSecond = OHIF.viewer.cine.framesPerSecond; - // If the movie is playing, stop/start to update the framerate - if (isPlaying()) { + if (viewportUtils.isPlaying()) { cornerstoneTools.stopClip(element); - cornerstoneTools.playClip(element); + cornerstoneTools.playClip(element, OHIF.viewer.cine.framesPerSecond); } Session.set('UpdateCINE', Random.id()); @@ -55,7 +55,7 @@ Template.cineDialog.onCreated(() => { instance.api = { displaySetPrevious: () => OHIF.viewer.moveDisplaySets(false), displaySetNext: () => OHIF.viewer.moveDisplaySets(true), - cineToggle: () => toggleCinePlay(), + cineToggle: () => viewportUtils.toggleCinePlay(), cineFirst: () => switchToImageByIndex(0), cineLast: () => switchToImageByIndex(-1), cinePrevious: () => switchToImageRelative(-1), @@ -80,13 +80,19 @@ Template.cineDialog.onCreated(() => { Tracker.afterFlush(() => { // Get the active viewportElement - const element = getActiveViewportElement(); + const element = viewportUtils.getActiveViewportElement(); if (!element) { return; } + // check if playClip tool has been initialized... + const playClipData = cornerstoneTools.getToolState(element, 'playClip'); + if (!playClipData) { + return; + } + // Get the cornerstone playClip tool data - const toolData = cornerstoneTools.getToolState(element, 'playClip').data[0]; + const toolData = playClipData.data[0]; // Get the cine object const cine = OHIF.viewer.cine; @@ -99,7 +105,10 @@ Template.cineDialog.onCreated(() => { cine.loop = _.isUndefined(cine.loop) ? true : cine.loop; // Set the updated data on the form inputs - instance.$('form:first').data('component').value(cine); + const elementComponent = instance.$('form:first').data('component'); + if (elementComponent) { + elementComponent.value(cine); + } // Update the session to refresh the framerate text Session.set('UpdateCINE', Random.id()); @@ -204,11 +213,9 @@ Template.cineDialog.onCreated(() => { }); Template.cineDialog.onRendered(() => { - const instance = Template.instance(); - - let dialog = instance.$('#cineDialog'), - singleRowLayout = OHIF.uiSettings.displayEchoUltrasoundWorkflow; + const dialog = instance.$('#cineDialog'); + const singleRowLayout = OHIF.uiSettings.displayEchoUltrasoundWorkflow; // set dialog in optimal position and make sure it continues in a optimal position... // ... when the window has been resized @@ -222,7 +229,10 @@ Template.cineDialog.onRendered(() => { instance.setResizeHandler(instance.setOptimalPosition); // Make the CINE dialog bounded and draggable - dialog.bounded().draggable({ defaultElementCursor: 'move' }); + dialog.draggable({ defaultElementCursor: 'move' }); + + // Polyfill for older browsers + dialogPolyfill.registerDialog(dialog.get(0)); // Prevent dialog from being dragged when user clicks any button dialog.find('.cine-navigation, .cine-controls, .cine-options').on('mousedown touchstart', function (e) { @@ -239,7 +249,7 @@ Template.cineDialog.onDestroyed(() => { Template.cineDialog.events({ 'change [data-key=loop] input'(event, instance) { - const element = getActiveViewportElement(); + const element = viewportUtils.getActiveViewportElement(); const playClipToolData = cornerstoneTools.getToolState(element, 'playClip'); playClipToolData.data[0].loop = $(event.currentTarget).is(':checked'); OHIF.viewer.cine.loop = playClipToolData.data[0].loop; @@ -254,7 +264,7 @@ Template.cineDialog.events({ Template.cineDialog.helpers({ isPlaying() { - return isPlaying(); + return viewportUtils.isPlaying(); }, framerate() { @@ -264,11 +274,11 @@ Template.cineDialog.helpers({ displaySetDisabled(isNext) { Session.get('LayoutManagerUpdated'); - return !OHIF.viewer.canMoveDisplaySets(isNext) ? 'disabled' : ''; + return !OHIF.viewerbase.layoutManager.canMoveDisplaySets(isNext) ? 'disabled' : ''; }, buttonDisabled() { - return hasMultipleFrames(); + return viewportUtils.hasMultipleFrames(); }, getClassNames(baseCls) { diff --git a/Packages/ohif-viewerbase/client/components/viewer/confirmDeleteDialog/confirmDeleteDialog.js b/Packages/ohif-viewerbase/client/components/viewer/confirmDeleteDialog/confirmDeleteDialog.js index 478339c80..291ae5006 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/confirmDeleteDialog/confirmDeleteDialog.js +++ b/Packages/ohif-viewerbase/client/components/viewer/confirmDeleteDialog/confirmDeleteDialog.js @@ -1,69 +1,26 @@ -function closeHandler() { - // Hide the lesion dialog - $('#confirmDeleteDialog').css('display', 'none'); +import { Template } from 'meteor/templating'; - // Remove the backdrop - $('.removableBackdrop').remove(); - - // Remove the callback from the template data - delete Template.confirmDeleteDialog.doneCallback; - - // Restore the focus to the active viewport - setFocusToActiveViewport(); -} - -/** - * Displays the confirmation dialog template and the removable backdrop element - * - * @param doneCallback A callback - * @param options - */ -showConfirmDialog = function(doneCallback, options) { - // Show the backdrop - options = options || {}; - UI.renderWithData(Template.removableBackdrop, options, document.body); - - var confirmDeleteDialog = $('#confirmDeleteDialog'); - confirmDeleteDialog.remove(); - - var viewer = document.getElementById('viewer'); - UI.renderWithData(Template.confirmDeleteDialog, options, viewer); - - // Make sure the context menu is closed when the user clicks away - $('.removableBackdrop').one('mousedown touchstart', function() { - closeHandler(); - }); - - confirmDeleteDialog = $('#confirmDeleteDialog'); - confirmDeleteDialog.css('display', 'block'); - confirmDeleteDialog.focus(); - - if (doneCallback && typeof doneCallback === 'function') { - Template.confirmDeleteDialog.doneCallback = doneCallback; - } -}; +import { dialogUtils } from '../../../lib/dialogUtils'; // Global object of key names (TODO: put this somewhere else) -keys = { +const keys = { ESC: 27, ENTER: 13 }; Template.confirmDeleteDialog.events({ - 'click #cancel, click #close': function() { - closeHandler(); + 'click #cancel, click #close'() { + // Action canceled, just close dialog without calling callback + dialogUtils.closeHandler(false); }, - 'click #confirm': function() { - var doneCallback = Template.confirmDeleteDialog.doneCallback; - if (doneCallback && typeof doneCallback === 'function') { - doneCallback(); - } - - closeHandler(); + 'click #confirm'() { + // Action confirmed, close dialog and calls callback, if exists + dialogUtils.closeHandler(); }, - 'keydown #confirmDeleteDialog': function(e) { + 'keydown #confirmDeleteDialog'(e) { + // Action canceled, just close dialog without calling callback if (e.which === keys.ESC) { - closeHandler(); + dialogUtils.closeHandler(false); return false; } @@ -71,15 +28,10 @@ Template.confirmDeleteDialog.events({ return; } - var doneCallback = Template.confirmDeleteDialog.doneCallback; - - // If Enter is pressed, close the dialog + // If Enter is pressed if (e.which === keys.ENTER) { - if (doneCallback && typeof doneCallback === 'function') { - doneCallback(); - } - - closeHandler(); + // Action confirmed, close dialog and calls callback, if exists + dialogUtils.closeHandler(); } } }); diff --git a/Packages/ohif-viewerbase/client/components/viewer/displaySetNavigation/displaySetNavigation.js b/Packages/ohif-viewerbase/client/components/viewer/displaySetNavigation/displaySetNavigation.js index c4e05e241..369fee6f6 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/displaySetNavigation/displaySetNavigation.js +++ b/Packages/ohif-viewerbase/client/components/viewer/displaySetNavigation/displaySetNavigation.js @@ -15,13 +15,13 @@ Template.displaySetNavigation.events({ return; } - OHIF.viewer.moveDisplaySets(false); + OHIF.viewerbase.layoutManager.moveDisplaySets(false); } }); Template.displaySetNavigation.helpers({ disableButton(isNext) { Session.get('LayoutManagerUpdated'); - return !OHIF.viewer.canMoveDisplaySets(isNext); + return !OHIF.viewerbase.layoutManager.canMoveDisplaySets(isNext); } }); diff --git a/Packages/ohif-viewerbase/client/components/viewer/imageControls/imageControls.js b/Packages/ohif-viewerbase/client/components/viewer/imageControls/imageControls.js index d2fce1971..abab6e649 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/imageControls/imageControls.js +++ b/Packages/ohif-viewerbase/client/components/viewer/imageControls/imageControls.js @@ -1,3 +1,8 @@ +import { Template } from 'meteor/templating'; +import { $ } from 'meteor/jquery'; +import { setActiveViewport } from '../../../lib/setActiveViewport'; +import { switchToImageByIndex } from '../../../lib/switchToImageByIndex'; + const slideTimeoutTime = 40; let slideTimeout; @@ -33,7 +38,7 @@ Template.imageControls.events({ OHIF.viewer.hotkeyFunctions.scrollUp(); } }, - 'input #imageSlider, change #imageSlider'(event) { + 'input input[type=range], change input[type=range]'(event) { // Note that we throttle requests to prevent the // user's ultrafast scrolling from firing requests too quickly. clearTimeout(slideTimeout); @@ -41,7 +46,7 @@ Template.imageControls.events({ // Using the slider in an inactive viewport // should cause that viewport to become active const slider = $(event.currentTarget); - const newActiveElement = slider.parents().eq(2).siblings('.imageViewerViewport').get(0); + const newActiveElement = slider.parents('.viewportContainer').find('.imageViewerViewport'); setActiveViewport(newActiveElement); // Subtract 1 here since the slider goes from 1 to N images diff --git a/Packages/ohif-viewerbase/client/components/viewer/layoutButton/layoutButton.js b/Packages/ohif-viewerbase/client/components/viewer/layoutButton/layoutButton.js index 06a54f7a2..65c726be6 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/layoutButton/layoutButton.js +++ b/Packages/ohif-viewerbase/client/components/viewer/layoutButton/layoutButton.js @@ -1,3 +1,7 @@ +import { Template } from 'meteor/templating'; +import { $ } from 'meteor/jquery'; +import { viewportUtils } from '../../../lib/viewportUtils'; + Template.layoutButton.events({ // TODO: Check why 'click' event won't fire? 'mousedown .js-dropdown-toggle'(event) { @@ -12,6 +16,6 @@ Template.layoutButton.events({ }); // Open or close the layout chooser dialog - toggleDialog($dropdown); + viewportUtils.toggleDialog($dropdown); } }); diff --git a/Packages/ohif-viewerbase/client/components/viewer/layoutChooser/layoutChooser.js b/Packages/ohif-viewerbase/client/components/viewer/layoutChooser/layoutChooser.js index 29a51ab91..892cf296c 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/layoutChooser/layoutChooser.js +++ b/Packages/ohif-viewerbase/client/components/viewer/layoutChooser/layoutChooser.js @@ -1,3 +1,9 @@ +import { Template } from 'meteor/templating'; +import { Session } from 'meteor/session'; +import { OHIF } from 'meteor/ohif:core'; +import 'meteor/ohif:viewerbase'; +import { viewportUtils } from '../../../lib/viewportUtils'; + Template.layoutChooser.onRendered(() => { const instance = Template.instance(); @@ -35,12 +41,12 @@ Template.layoutChooser.onRendered(() => { // Refresh layout chooser highlighting based on current viewports state instance.refreshHighlights = () => { // Stop here if layoutManager is not defined yet - if (!window.layoutManager) { + if (!OHIF.viewerbase.layoutManager) { return; } // Get the layout rows and columns amount - const info = window.layoutManager.layoutProps; + const info = OHIF.viewerbase.layoutManager.layoutProps; // get the limiter cell const cell = instance.$('tr').eq(info.rows - 1).children().eq(info.columns - 1); @@ -77,11 +83,11 @@ Template.layoutChooser.events({ columns: columnIndex + 1 }; - window.layoutManager.layoutTemplateName = 'gridLayout'; - window.layoutManager.layoutProps = layoutProps; - window.layoutManager.updateViewports(); + OHIF.viewerbase.layoutManager.layoutTemplateName = 'gridLayout'; + OHIF.viewerbase.layoutManager.layoutProps = layoutProps; + OHIF.viewerbase.layoutManager.updateViewports(); const $dropdown = instance.$('.layoutChooser'); - toggleDialog($dropdown); + viewportUtils.toggleDialog($dropdown); } }); diff --git a/Packages/ohif-viewerbase/client/components/viewer/loadingIndicator/loadingIndicator.js b/Packages/ohif-viewerbase/client/components/viewer/loadingIndicator/loadingIndicator.js index 94d407b12..cdba0432e 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/loadingIndicator/loadingIndicator.js +++ b/Packages/ohif-viewerbase/client/components/viewer/loadingIndicator/loadingIndicator.js @@ -1,3 +1,6 @@ +import { Meteor } from 'meteor/meteor'; +import { Template } from 'meteor/templating'; +import { $ } from 'meteor/jquery'; import { OHIF } from 'meteor/ohif:core'; Meteor.startup(function() { @@ -8,43 +11,41 @@ Meteor.startup(function() { var loadHandlerTimeout; -startLoadingHandler = function(element) { +const startLoadingHandler = function(element) { clearTimeout(loadHandlerTimeout); loadHandlerTimeout = setTimeout(function() { console.log('startLoading'); var elem = $(element); elem.siblings('.imageViewerErrorLoadingIndicator').css('display', 'none'); - elem.find('canvas').not('.magnifyTool').addClass("faded"); + elem.find('canvas').not('.magnifyTool').addClass('faded'); elem.siblings('.imageViewerLoadingIndicator').css('display', 'block'); }, OHIF.viewer.loadIndicatorDelay); }; -doneLoadingHandler = function(element) { +const doneLoadingHandler = function(element) { clearTimeout(loadHandlerTimeout); var elem = $(element); elem.siblings('.imageViewerErrorLoadingIndicator').css('display', 'none'); - elem.find('canvas').not('.magnifyTool').removeClass("faded"); + elem.find('canvas').not('.magnifyTool').removeClass('faded'); elem.siblings('.imageViewerLoadingIndicator').css('display', 'none'); }; -errorLoadingHandler = function(element, imageId, error, source) { +const errorLoadingHandler = function(element, imageId, error, source) { clearTimeout(loadHandlerTimeout); var elem = $(element); // Could probably chain all of these, but this is more readable - elem.find('canvas').not('.magnifyTool').removeClass("faded"); + elem.find('canvas').not('.magnifyTool').removeClass('faded'); elem.siblings('.imageViewerLoadingIndicator').css('display', 'none'); // Don't display errors from the stackPrefetch tool - if (source === "stackPrefetch") { + if (source === 'stackPrefetch') { return; } var errorLoadingIndicator = elem.siblings('.imageViewerErrorLoadingIndicator'); errorLoadingIndicator.css('display', 'block'); - var cleanedImageId = imageId; - // This is just used to expand upon some error messages that are sent // when things fail. An example is a network error throwing the error // which is only described as "network". @@ -57,7 +58,7 @@ errorLoadingHandler = function(element, imageId, error, source) { error = errorDetails[error]; } - errorLoadingIndicator.find('.description').text("An error has occurred while loading image: " + cleanedImageId); + errorLoadingIndicator.find('.description').text("An error has occurred while loading image: " + imageId); if (error) { errorLoadingIndicator.find('.details').text("Details: " + error); } diff --git a/Packages/ohif-viewerbase/client/components/viewer/playClipButton/playClipButton.js b/Packages/ohif-viewerbase/client/components/viewer/playClipButton/playClipButton.js index ec9b175de..a9196ec70 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/playClipButton/playClipButton.js +++ b/Packages/ohif-viewerbase/client/components/viewer/playClipButton/playClipButton.js @@ -1,8 +1,11 @@ +import { Template } from 'meteor/templating'; +import { viewportUtils } from '../../../lib/viewportUtils'; + Template.playClipButton.helpers({ isPlaying: function() { - return isPlaying(); + return viewportUtils.isPlaying(); }, disableButton() { - return hasMultipleFrames(); + return viewportUtils.hasMultipleFrames(); } }); diff --git a/Packages/ohif-viewerbase/client/components/viewer/studySeriesQuickSwitch/studySeriesQuickSwitch.js b/Packages/ohif-viewerbase/client/components/viewer/studySeriesQuickSwitch/studySeriesQuickSwitch.js index 627244ee6..df78e8c6a 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/studySeriesQuickSwitch/studySeriesQuickSwitch.js +++ b/Packages/ohif-viewerbase/client/components/viewer/studySeriesQuickSwitch/studySeriesQuickSwitch.js @@ -1,6 +1,10 @@ import { Template } from 'meteor/templating'; import { ReactiveVar } from 'meteor/reactive-var'; import { Session } from 'meteor/session'; +import { $ } from 'meteor/jquery'; + +import { OHIF } from 'meteor/ohif:core'; +import 'meteor/ohif:viewerbase'; Template.studySeriesQuickSwitch.onCreated(() => { const instance = Template.instance(); @@ -10,7 +14,7 @@ Template.studySeriesQuickSwitch.onCreated(() => { // Gets the viewport data for the given viewport index instance.getViewportData = viewportIndex => { - const layoutManager = window.layoutManager; + const layoutManager = OHIF.viewerbase.layoutManager; return layoutManager && layoutManager.viewportData && layoutManager.viewportData[viewportIndex]; }; diff --git a/Packages/ohif-viewerbase/client/components/viewer/studyTimepointBrowser/studyTimepointBrowser.js b/Packages/ohif-viewerbase/client/components/viewer/studyTimepointBrowser/studyTimepointBrowser.js index ca02b2412..5504757fc 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/studyTimepointBrowser/studyTimepointBrowser.js +++ b/Packages/ohif-viewerbase/client/components/viewer/studyTimepointBrowser/studyTimepointBrowser.js @@ -3,6 +3,8 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; import { _ } from 'meteor/underscore'; +import { OHIFError } from '../../../lib/classes/OHIFError'; + Template.studyTimepointBrowser.onCreated(() => { const instance = Template.instance(); @@ -36,7 +38,7 @@ Template.studyTimepointBrowser.onCreated(() => { const notYetLoaded = StudyListStudies.findOne(query); if (!notYetLoaded) { - throw 'No study data available for Study: ' + studyInstanceUid; + throw new OHIFError(`No study data available for Study: ${studyInstanceUid}`); } return notYetLoaded; diff --git a/Packages/ohif-viewerbase/client/components/viewer/studyTimepointBrowser/studyTimepointStudy.js b/Packages/ohif-viewerbase/client/components/viewer/studyTimepointBrowser/studyTimepointStudy.js index 75d8c54a0..3e594658e 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/studyTimepointBrowser/studyTimepointStudy.js +++ b/Packages/ohif-viewerbase/client/components/viewer/studyTimepointBrowser/studyTimepointStudy.js @@ -3,6 +3,7 @@ import { Template } from 'meteor/templating'; import { ReactiveVar } from 'meteor/reactive-var'; import { _ } from 'meteor/underscore'; import { $ } from 'meteor/jquery'; +import { sortingManager } from '../../../lib/sortingManager'; Template.studyTimepointStudy.onCreated(() => { const instance = Template.instance(); @@ -16,11 +17,6 @@ Template.studyTimepointStudy.onCreated(() => { return isGlobal ? $(selector) : instance.$browser.find(selector); }; - // Set all the studies with the same uid to loading state - instance.setLoading = () => { - - }; - // Set the current study as selected in the studies list instance.select = (isQuickSwitch=false) => { const studyInstanceUid = instance.data.study.studyInstanceUid; @@ -110,7 +106,7 @@ Template.studyTimepointStudy.events({ const $studies = instance.getStudyElement(true); $studies.trigger('loadStarted'); getStudyMetadata(studyInstanceUid, study => { - study.displaySets = createStacks(study); + study.displaySets = sortingManager.getDisplaySets(study); instance.data.study = study; ViewerStudies.insert(study, () => { // To make sure studies are rendered in the DOM diff --git a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js index 962b4648e..1d8d88522 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js +++ b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js @@ -2,6 +2,7 @@ import { OHIF } from 'meteor/ohif:core'; import { Template } from 'meteor/templating'; import { Session } from 'meteor/session'; import { _ } from 'meteor/underscore'; +import { toolManager } from '../../../lib/toolManager'; Template.toolbarSectionButton.onCreated(() => { const instance = Template.instance(); diff --git a/Packages/ohif-viewerbase/client/components/viewer/viewerMain/viewerMain.js b/Packages/ohif-viewerbase/client/components/viewer/viewerMain/viewerMain.js index c009cca78..661534958 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/viewerMain/viewerMain.js +++ b/Packages/ohif-viewerbase/client/components/viewer/viewerMain/viewerMain.js @@ -1,5 +1,20 @@ +import { Meteor } from 'meteor/meteor'; +import { Template } from 'meteor/templating'; +import { Session } from 'meteor/session'; import { OHIF } from 'meteor/ohif:core'; -import { unloadHandlers } from '../../../lib/unloadHandlers.js'; +// Local Modules +import { toolManager } from '../../../lib/toolManager'; +import { unloadHandlers } from '../../../lib/unloadHandlers'; +import { hotkeyUtils } from '../../../lib/hotkeyUtils'; +import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManager'; +import { LayoutManager } from '../../../lib/classes/LayoutManager'; + +Meteor.startup(() => { + // Create the synchronizer used to update reference lines + OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('CornerstoneNewImage', cornerstoneTools.updateImageSynchronizer); + + window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager(); +}); Template.viewerMain.onCreated(() => { // Attach the Window resize listener @@ -8,23 +23,22 @@ Template.viewerMain.onCreated(() => { // and change it to jQuery._data(window, 'events')['resize']. // Otherwise this function will be probably overrided. // See cineDialog instance.setResizeHandler function - window.addEventListener('resize', handleResize); + window.addEventListener('resize', window.ResizeViewportManager.handleResize.bind(window.ResizeViewportManager)); // Add beforeUnload event handler to check for unsaved changes window.addEventListener('beforeunload', unloadHandlers.beforeUnload); - - // Create the synchronizer used to update reference lines - OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('CornerstoneNewImage', cornerstoneTools.updateImageSynchronizer); }); Template.viewerMain.onRendered(() => { const instance = Template.instance(); - HP.ProtocolStore.onReady(() => { + instance.subscribe('hangingprotocols', () => { const { studies, currentTimepointId, measurementApi, timepointIds } = instance.data; const parentElement = instance.$('#layoutManagerTarget').get(0); - window.layoutManager = new LayoutManager(parentElement, studies); + OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies); + + OHIF.viewerbase.layoutManager.updateViewports(); // Default actions for Associated Studies if(currentTimepointId) { // Follow-up studies: same as the first measurement in the table @@ -73,18 +87,22 @@ Template.viewerMain.onRendered(() => { } // Toggle Measurement Table - instance.data.state.set('rightSidebar', 'measurements'); + if(instance.data.state) { + instance.data.state.set('rightSidebar', 'measurements'); + } } // Hide as default for single study else { - instance.data.state.set('rightSidebar', null); + if(instance.data.state) { + instance.data.state.set('rightSidebar', null); + } } - ProtocolEngine = new HP.ProtocolEngine(window.layoutManager, studies); - HP.setEngine(ProtocolEngine); + // ProtocolEngine = new HP.ProtocolEngine(OHIF.viewerbase.layoutManager, studies); + // HP.setEngine(ProtocolEngine); // Enable hotkeys - enableHotkeys(); + hotkeyUtils.enableHotkeys(); Session.set('ViewerMainReady', Random.id()); }); @@ -94,7 +112,7 @@ Template.viewerMain.onDestroyed(() => { OHIF.log.info('viewerMain onDestroyed'); // Remove the Window resize listener - window.removeEventListener('resize', handleResize); + window.removeEventListener('resize', window.ResizeViewportManager.handleResize.bind(window.ResizeViewportManager)); // Remove beforeUnload event handler... window.removeEventListener('beforeunload', unloadHandlers.beforeUnload); @@ -102,6 +120,6 @@ Template.viewerMain.onDestroyed(() => { // Destroy the synchronizer used to update reference lines OHIF.viewer.updateImageSynchronizer.destroy(); - delete window.layoutManager; + delete OHIF.viewerbase.layoutManager; delete ProtocolEngine; }); diff --git a/Packages/ohif-viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.js b/Packages/ohif-viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.js index 82ce4d8a3..195ce9c5d 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.js +++ b/Packages/ohif-viewerbase/client/components/viewer/viewportOverlay/viewportOverlay.js @@ -1,253 +1,147 @@ -function getElementIfNotEmpty(viewportIndex) { - // Meteor template helpers run more often than expected - // They often seem to run just before the whole template is rendered - // This meant that the onRendered event hadn't fired yet, so the - // element wasn't enabled / set empty yet. The check here - // for canvases under the 'enabled' element div is to prevent - // 'undefined' errors from the helper functions - - var imageViewerViewports = $('.imageViewerViewport'), - element = imageViewerViewports.get(viewportIndex), - canvases = imageViewerViewports.eq(viewportIndex).find('canvas'); - - if (!element || $(element).hasClass('empty') || canvases.length === 0) { - return; - } - - // Check to make sure the element is enabled. - try { - var enabledElement = cornerstone.getEnabledElement(element); - } catch(error) { - return; - } - - return element; -} - -function getStackDataIfNotEmpty(viewportIndex) { - const element = getElementIfNotEmpty(viewportIndex); - if (!element) { - return; - } - - const stackToolData = cornerstoneTools.getToolState(element, 'stack'); - if (!stackToolData || - !stackToolData.data || - !stackToolData.data.length) { - return; - } - - const stack = stackToolData.data[0]; - if (!stack) { - return; - } - - return stack; -} - -function getPatient(property) { - Session.get('CornerstoneNewImage' + this.viewportIndex); - if (!this.imageId) { - return false; - } - - var patient = cornerstoneTools.metaData.get('patient', this.imageId); - if (!patient) { - return ''; - } - - return patient[property]; -} - -function getStudy(property) { - Session.get('CornerstoneNewImage' + this.viewportIndex); - if (!this.imageId) { - return false; - } - - var study = cornerstoneTools.metaData.get('study', this.imageId); - if (!study) { - return ''; - } - - return study[property]; -} - -function getSeries(property) { - Session.get('CornerstoneNewImage' + this.viewportIndex); - if (!this.imageId) { - return false; - } - - var series = cornerstoneTools.metaData.get('series', this.imageId); - if (!series) { - return ''; - } - - return series[property]; -} - -function getInstance(property) { - Session.get('CornerstoneNewImage' + this.viewportIndex); - if (!this.imageId) { - return false; - } - - var instance = cornerstoneTools.metaData.get('instance', this.imageId); - if (!instance) { - return ''; - } - - return instance[property]; -} - -function getTagDisplay(property) { - Session.get('CornerstoneNewImage' + this.viewportIndex); - if (!this.imageId) { - return false; - } - - var instance = cornerstoneTools.metaData.get('tagDisplay', this.imageId); - if (!instance) { - return ''; - } - - return instance[property]; -} - -function getImage(viewportIndex) { - var element = getElementIfNotEmpty(viewportIndex); - if (!element) { - return false; - } - - var enabledElement; - try { - enabledElement = cornerstone.getEnabledElement(element); - } catch(error) { - return false; - } - - if (!enabledElement || !enabledElement.image) { - return false; - } - - return enabledElement.image; -} - -function formatDateTime(date, time) { - return `${date} ${time}`; -} +import { Template } from 'meteor/templating'; +import { Session } from 'meteor/session'; +import { viewportOverlayUtils } from '../../../lib/viewportOverlayUtils'; +import { getElementIfNotEmpty } from '../../../lib/getElementIfNotEmpty'; +import { getStackDataIfNotEmpty } from '../../../lib/getStackDataIfNotEmpty'; Template.viewportOverlay.helpers({ - wwwc: function() { + wwwc() { Session.get('CornerstoneImageRendered' + this.viewportIndex); - var element = getElementIfNotEmpty(this.viewportIndex); + + const element = getElementIfNotEmpty(this.viewportIndex); if (!element) { return ''; } - var viewport = cornerstone.getViewport(element); + const viewport = cornerstone.getViewport(element); if (!viewport) { return ''; } return 'W ' + viewport.voi.windowWidth.toFixed(0) + ' L ' + viewport.voi.windowCenter.toFixed(0); }, - zoom: function() { + zoom() { Session.get('CornerstoneImageRendered' + this.viewportIndex); - var element = getElementIfNotEmpty(this.viewportIndex); + + const element = getElementIfNotEmpty(this.viewportIndex); if (!element) { return; } - var viewport = cornerstone.getViewport(element); + const viewport = cornerstone.getViewport(element); if (!viewport) { return; } return (viewport.scale * 100.0); }, - imageDimensions: function() { + imageDimensions() { Session.get('CornerstoneNewImage' + this.viewportIndex); - var image = getImage(this.viewportIndex); + const image = viewportOverlayUtils.getImage(this.viewportIndex); if (!image) { return ''; } return image.width + ' x ' + image.height; }, - patientName: function() { - return getPatient.call(this, 'name'); + patientName() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getPatient.call(this, 'name'); }, - patientId: function() { - return getPatient.call(this, 'id'); + patientId() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getPatient.call(this, 'id'); }, - patientBirthDate: function() { - return getPatient.call(this, 'birthDate'); + patientBirthDate() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getPatient.call(this, 'birthDate'); }, - patientSex: function() { - return getPatient.call(this, 'sex'); + patientSex() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getPatient.call(this, 'sex'); }, - studyDate: function() { - return getStudy.call(this, 'studyDate'); + studyDate() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getStudy.call(this, 'studyDate'); }, - studyTime: function() { - return getStudy.call(this, 'studyTime'); + studyTime() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getStudy.call(this, 'studyTime'); }, - studyDescription: function() { - return getStudy.call(this, 'studyDescription'); + studyDescription() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getStudy.call(this, 'studyDescription'); }, - seriesDescription: function() { - return getSeries.call(this, 'seriesDescription'); + seriesDescription() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getSeries.call(this, 'seriesDescription'); }, - frameRate: function() { - var frameTime = getInstance.call(this, 'frameTime'); + frameRate() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + const frameTime = viewportOverlayUtils.getInstance.call(this, 'frameTime'); if (!frameTime) { return; } - - var frameRate = 1000 / frameTime; + + const frameRate = 1000 / frameTime; return frameRate.toFixed(1); }, - seriesNumber: function() { - return getSeries.call(this, 'seriesNumber'); + seriesNumber() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getSeries.call(this, 'seriesNumber'); }, - instanceNumber: function() { - return getInstance.call(this, 'instanceNumber'); + instanceNumber() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getInstance.call(this, 'instanceNumber'); }, thickness() { // Displays Slice Thickness (0018,0050) - return getInstance.call(this, 'sliceThickness'); - }, + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getInstance.call(this, 'sliceThickness'); + }, location() { // Displays Slice Location (0020,1041), if present. // - Otherwise, displays Table Position (0018,9327) // - TODO: Otherwise, displays a value derived from Image Position (Patient) (0020,0032) - const sliceLocation = getInstance.call(this, 'sliceLocation'); + + Session.get('CornerstoneNewImage' + this.viewportIndex); + const sliceLocation = viewportOverlayUtils.getInstance.call(this, 'sliceLocation'); if (sliceLocation !== '') { return sliceLocation; } - const tablePosition = getInstance.call(this, 'tablePosition'); + const tablePosition = viewportOverlayUtils.getInstance.call(this, 'tablePosition'); if (tablePosition !== '') { return tablePosition; } - return getInstance.call(this, 'imagePositionPatient'); + return viewportOverlayUtils.getInstance.call(this, 'imagePositionPatient'); }, - spacingBetweenSlices() { // Displays Spacing Between Slices (0018,0088), if present. // TODO: Otherwise, displays a value derived from successive values // of Image Position (Patient) (0020,0032) perpendicular to // the Image Orientation (Patient) (0020,0037) - return getInstance.call(this, 'spacingBetweenSlices'); - }, + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getInstance.call(this, 'spacingBetweenSlices'); + }, compression() { // Displays whether or not lossy compression has been applied: // @@ -256,11 +150,12 @@ Template.viewportOverlay.helpers({ // and Lossy Image Compression Method (0028,2114) Session.get('CornerstoneNewImage' + this.viewportIndex); + if (!this.imageId) { return false; } - var instance = cornerstoneTools.metaData.get('instance', this.imageId); + const instance = cornerstoneTools.metaData.get('instance', this.imageId); if (!instance) { return ''; } @@ -274,43 +169,56 @@ Template.viewportOverlay.helpers({ return 'Lossless / Uncompressed'; }, - - tagDisplayLeftOnly: function() { - return getTagDisplay.call(this, 'side') === 'L'; - }, - tagDisplayRightOnly: function() { - return getTagDisplay.call(this, 'side') === 'R'; - }, - tagDisplaySpecified: function() { - return getTagDisplay.call(this, 'side'); - }, - imageIndex: function() { + tagDisplayLeftOnly() { Session.get('CornerstoneNewImage' + this.viewportIndex); - var stack = getStackDataIfNotEmpty(this.viewportIndex); + + return viewportOverlayUtils.getTagDisplay.call(this, 'side') === 'L'; + }, + tagDisplayRightOnly() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getTagDisplay.call(this, 'side') === 'R'; + }, + tagDisplaySpecified() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getTagDisplay.call(this, 'side'); + }, + imageNumber() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + return viewportOverlayUtils.getInstance.call(this, 'number'); + }, + imageIndex() { + Session.get('CornerstoneNewImage' + this.viewportIndex); + + const stack = getStackDataIfNotEmpty(this.viewportIndex); if (!stack || stack.currentImageIdIndex === undefined) { return; } return stack.currentImageIdIndex + 1; }, - numImages: function() { + numImages() { Session.get('CornerstoneNewImage' + this.viewportIndex); - var stack = getStackDataIfNotEmpty(this.viewportIndex); + + const stack = getStackDataIfNotEmpty(this.viewportIndex); if (!stack || !stack.imageIds) { return; } return stack.imageIds.length; }, - prior: function() { + prior() { // This helper is updated whenever a new image is displayed in the viewport Session.get('CornerstoneNewImage' + this.viewportIndex); + if (!this.imageId) { return; } // Make sure there are more than two studies loaded in the viewer - var viewportStudies = ViewerStudies.find(); + const viewportStudies = ViewerStudies.find(); if (viewportStudies.count() < 2) { return; } @@ -319,18 +227,18 @@ Template.viewportOverlay.helpers({ // that we can obtain the oldest study as the first element of the array // // TODO= Find out if we should encode studyDate as a Date in the ViewerStudies Collection - var viewportStudiesArray = _.sortBy(viewportStudies.fetch(), function(study) { - return formatDateTime(study.studyDate, study.studyTime); + const viewportStudiesArray = _.sortBy(viewportStudies.fetch(), function(study) { + return viewportOverlayUtils.formatDateTime(study.studyDate, study.studyTime); }); // Get study data - var study = cornerstoneTools.metaData.get('study', this.imageId); + const study = cornerstoneTools.metaData.get('study', this.imageId); if (!study) { return; } - var oldestStudy = viewportStudiesArray[0]; - if (formatDateTime(study.studyDate, study.studyTime) <= formatDateTime(oldestStudy.studyDate, oldestStudy.studyTime)) { + const oldestStudy = viewportStudiesArray[0]; + if (viewportOverlayUtils.formatDateTime(study.studyDate, study.studyTime) <= viewportOverlayUtils.formatDateTime(oldestStudy.studyDate, oldestStudy.studyTime)) { return 'Prior'; } } diff --git a/Packages/ohif-viewerbase/client/index.js b/Packages/ohif-viewerbase/client/index.js index 2cdbf046f..3323b92ef 100644 --- a/Packages/ohif-viewerbase/client/index.js +++ b/Packages/ohif-viewerbase/client/index.js @@ -185,7 +185,8 @@ import { OHIFError } from './lib/classes/OHIFError'; Viewerbase.OHIFError = OHIFError; /** - * Imports for Side Effects Only + * Imports for Side Effects Only (Files that do not export anything...) */ +import './lib/stackImagePositionOffsetSynchronizer.js'; import './lib/debugReactivity'; diff --git a/Packages/ohif-viewerbase/client/lib/WLPresets.js b/Packages/ohif-viewerbase/client/lib/WLPresets.js index 92a7df21a..10276df88 100644 --- a/Packages/ohif-viewerbase/client/lib/WLPresets.js +++ b/Packages/ohif-viewerbase/client/lib/WLPresets.js @@ -1,28 +1,86 @@ +import { Meteor } from 'meteor/meteor'; import { OHIF } from 'meteor/ohif:core'; +import { viewportUtils } from './viewportUtils'; -applyWLPreset = function(presetName, element) { - OHIF.log.info("Applying WL Preset: " + presetName); - var viewport = cornerstone.getViewport(element); +// TODO: add this to a namespace definition +Meteor.startup(function() { + OHIF.viewer.defaultWLPresets = { + SoftTissue: { + wc: 40, + ww: 400 + }, + Lung: { + wc: -600, + ww: 1500 + }, + Liver: { + wc: 90, + ww: 150 + }, + Bone: { + wc: 480, + ww: 2500 + }, + Brain: { + wc: 40, + ww: 80 + } + }; - if (presetName === 'Default') { - var enabledElement = cornerstone.getEnabledElement(element); - viewport.voi.windowWidth = enabledElement.image.windowWidth; - viewport.voi.windowCenter = enabledElement.image.windowCenter; - } else { - var preset = OHIF.viewer.wlPresets[presetName]; - viewport.voi.windowWidth = preset.ww; - viewport.voi.windowCenter = preset.wc; + // For now + OHIF.viewer.wlPresets = OHIF.viewer.defaultWLPresets; +}); + +function applyWLPreset(presetName, element) { + OHIF.log.info('Applying WL Preset: ' + presetName); + if (presetName !== 'Custom') { + const viewport = cornerstone.getViewport(element); + + if (presetName === 'Default') { + const enabledElement = cornerstone.getEnabledElement(element); + viewport.voi.windowWidth = enabledElement.image.windowWidth; + viewport.voi.windowCenter = enabledElement.image.windowCenter; + } else { + const preset = OHIF.viewer.wlPresets[presetName]; + if(preset) { + viewport.voi.windowWidth = preset.ww; + viewport.voi.windowCenter = preset.wc; + } + } + + // Update the viewport + cornerstone.setViewport(element, viewport); } +} - // Update the viewport - cornerstone.setViewport(element, viewport); -}; - -applyWLPresetToActiveElement = function(presetName) { - var element = getActiveViewportElement(); +function applyWLPresetToActiveElement(presetName) { + const element = viewportUtils.getActiveViewportElement(); if (!element) { return; } applyWLPreset(presetName, element); + + // To perform reactivity in other components + Session.set('WLPresetActiveElement', presetName); +} + +/** + * Overrides OHIF's wlPresets + * @param {Object} wlPresets Object with wlPresets mapping + */ +function setOHIFWLPresets(wlPresets) { + OHIF.viewer.wlPresets = wlPresets; +} + +/** + * Export functions inside WLPresets namespace. + */ + +const WLPresets = { + applyWLPreset, + applyWLPresetToActiveElement, + setOHIFWLPresets }; + +export { WLPresets }; diff --git a/Packages/ohif-viewerbase/client/lib/createStacks.js b/Packages/ohif-viewerbase/client/lib/createStacks.js index a178537d4..86297f8ef 100644 --- a/Packages/ohif-viewerbase/client/lib/createStacks.js +++ b/Packages/ohif-viewerbase/client/lib/createStacks.js @@ -1,3 +1,6 @@ +import { ImageSet } from './classes/ImageSet'; +import { isImage } from './isImage'; + /** * Creates a set of series to be placed in the Study Browser * The series that appear in the Study Browser must represent @@ -10,7 +13,7 @@ * @param study The study instance to be used * @returns {Array} An array of series to be placed in the Study Browser */ -createStacks = function(study) { +function createStacks(study) { // Define an empty array of display sets var displaySets = []; @@ -38,33 +41,21 @@ createStacks = function(study) { let displaySet; if (isMultiFrame(instance)) { displaySet = makeDisplaySet(series, [ instance ]); - displaySet.isClip = true; - - // Include the study instance Uid for drag/drop purposes - displaySet.studyInstanceUid = study.studyInstanceUid; - - // Override the default value of instances.length - displaySet.numImageFrames = instance.numFrames; - - // Include the instance number - displaySet.instanceNumber = instance.instanceNumber; - - // Include the acquisition datetime - displaySet.acquisitionDatetime = instance.acquisitionDatetime; - + displaySet.setAttributes({ + isClip: true, + studyInstanceUid: study.studyInstanceUid, // Include the study instance Uid for drag/drop purposes + numImageFrames: instance.numFrames, // Override the default value of instances.length + instanceNumber: instance.instanceNumber, // Include the instance number + acquisitionDatetime: instance.acquisitionDatetime // Include the acquisition datetime + }); displaySets.push(displaySet); } else if (isSingleImageModality(instance.modality)) { displaySet = makeDisplaySet(series, [ instance ]); - - // Include the study instance Uid - displaySet.studyInstanceUid = study.studyInstanceUid; - - // Include the instance number - displaySet.instanceNumber = instance.instanceNumber; - - // Include the acquisition datetime - displaySet.acquisitionDatetime = instance.acquisitionDatetime; - + displaySet.setAttributes({ + studyInstanceUid: study.studyInstanceUid, // Include the study instance Uid + instanceNumber: instance.instanceNumber, // Include the instance number + acquisitionDatetime: instance.acquisitionDatetime // Include the acquisition datetime + }); displaySets.push(displaySet); } else { stackableInstances.push(instance); @@ -73,7 +64,7 @@ createStacks = function(study) { if (stackableInstances.length) { let displaySet = makeDisplaySet(series, stackableInstances); - displaySet.studyInstanceUid = study.studyInstanceUid; + displaySet.setAttribute('studyInstanceUid', study.studyInstanceUid); displaySets.push(displaySet); } }); @@ -84,32 +75,32 @@ createStacks = function(study) { function makeDisplaySet(series, instances) { const instance = instances[0]; - let displaySet = { + const imageSet = new ImageSet(instances); + + // set appropriate attributes to image set... + imageSet.setAttributes({ + displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID seriesInstanceUid: series.seriesInstanceUid, seriesNumber: series.seriesNumber, seriesDescription: series.seriesDescription, numImageFrames: instances.length, frameRate: instance.frameTime, - images: instances, modality: series.modality, isMultiFrame: isMultiFrame(instance) - }; + }); // Sort the images in this series - displaySet.images.sort(function(a, b) { + imageSet.sortBy(function(a, b) { if (a.instanceNumber && b.instanceNumber && a.instanceNumber !== b.instanceNumber) { return a.instanceNumber - b.instanceNumber; } }); - // Include the first image instance number - displaySet.instanceNumber = displaySet.images[0].instanceNumber; + // Include the first image instance number (after sorted) + imageSet.setAttribute('instanceNumber', imageSet.getImage(0).instanceNumber); - // Create a unique ID for this stack so we can reference it - displaySet.displaySetInstanceUid = Random.id(); - - return displaySet; + return imageSet; } function isSingleImageModality(modality) { @@ -121,3 +112,9 @@ function isSingleImageModality(modality) { function isMultiFrame(instance) { return instance.numFrames > 1; } + +/** + * Expose "createStacks"... + */ + +export { createStacks }; diff --git a/Packages/ohif-viewerbase/client/lib/displayReferenceLines.js b/Packages/ohif-viewerbase/client/lib/displayReferenceLines.js index 1cdf42e6a..8cc4cb7e3 100644 --- a/Packages/ohif-viewerbase/client/lib/displayReferenceLines.js +++ b/Packages/ohif-viewerbase/client/lib/displayReferenceLines.js @@ -7,21 +7,30 @@ import { OHIF } from 'meteor/ohif:core'; * * @param element {node} DOM Node representing the viewport element */ -displayReferenceLines = element => { - OHIF.log.info("imageViewerViewport displayReferenceLines"); +export function displayReferenceLines(element) { // Check if image plane (orientation / loction) data is present for the current image const enabledElement = cornerstone.getEnabledElement(element); - const imageId = enabledElement.image.imageId; - const imagePlane = cornerstoneTools.metaData.get('imagePlane', imageId); - if (!OHIF.viewer.refLinesEnabled || !imagePlane || !imagePlane.frameOfReferenceUID) { + // Check if element is already enabled and it's image was rendered + if(!enabledElement || !enabledElement.image) { + OHIF.log.info('displayReferenceLines enabled element is undefined or it\'s image is not rendered'); return; } + const imageId = enabledElement.image.imageId; + const imagePlane = cornerstoneTools.metaData.get('imagePlane', imageId); + // Disable reference lines for the current element cornerstoneTools.referenceLines.tool.disable(element); + if (!OHIF.viewer.refLinesEnabled || !imagePlane || !imagePlane.frameOfReferenceUID) { + OHIF.log.info('displayReferenceLines refLinesEnabled is not enabled, no imagePlane or no frameOfReferenceUID'); + return; + } + + OHIF.log.info(`displayReferenceLines for image with id: ${imageId}`); + // Loop through all other viewport elements and enable reference lines $('.imageViewerViewport').not(element).each((index, viewportElement) => { let imageId; @@ -40,4 +49,4 @@ displayReferenceLines = element => { cornerstoneTools.referenceLines.tool.enable(viewportElement, OHIF.viewer.updateImageSynchronizer); } }); -}; +} diff --git a/Packages/ohif-viewerbase/client/lib/getImageId.js b/Packages/ohif-viewerbase/client/lib/getImageId.js index 20b13ab61..c9526ff4b 100644 --- a/Packages/ohif-viewerbase/client/lib/getImageId.js +++ b/Packages/ohif-viewerbase/client/lib/getImageId.js @@ -1,4 +1,5 @@ import { Meteor } from "meteor/meteor"; +import { getWADORSImageId } from './getWADORSImageId'; /** * Obtain an imageId for Cornerstone from an image instance @@ -6,17 +7,17 @@ import { Meteor } from "meteor/meteor"; * @param instance * @returns {string} The imageId to be used by Cornerstone */ -getImageId = function(instance, frame) { +export function getImageId(instance, frame) { if (!instance) { return; } if (instance.url) { - return instance.url; + return instance.url; } if (instance.wadouri) { - var imageId = 'dicomweb:' + Meteor.absoluteUrl(instance.wadouri); // WADO-URI; + var imageId = 'dicomweb:' + Meteor.absoluteUrl(instance.wadouri); if (frame !== undefined) { imageId += '&frame=' + frame; } @@ -26,4 +27,4 @@ getImageId = function(instance, frame) { // TODO= Check multiframe image support with WADO-RS return getWADORSImageId(instance); // WADO-RS Retrieve Frame } -}; \ No newline at end of file +} diff --git a/Packages/ohif-viewerbase/client/lib/getWADORSImageId.js b/Packages/ohif-viewerbase/client/lib/getWADORSImageId.js index 6b17a5acd..e52146394 100644 --- a/Packages/ohif-viewerbase/client/lib/getWADORSImageId.js +++ b/Packages/ohif-viewerbase/client/lib/getWADORSImageId.js @@ -8,7 +8,7 @@ import { OHIF } from 'meteor/ohif:core'; * @returns {string} The imageId to be used by Cornerstone */ -getWADORSImageId = function(instance) { +export function getWADORSImageId(instance) { var columnPixelSpacing = 1.0; var rowPixelSpacing = 1.0; if (instance.pixelSpacing) { diff --git a/Packages/ohif-viewerbase/client/lib/helpers/capitalizeFirstLetter.js b/Packages/ohif-viewerbase/client/lib/helpers/capitalizeFirstLetter.js index e872649b7..3e7378898 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/capitalizeFirstLetter.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/capitalizeFirstLetter.js @@ -1,3 +1,5 @@ +import { Blaze } from 'meteor/blaze'; + /** * A global Blaze UI helper to capitalizes the first letter of an input String * @@ -5,10 +7,10 @@ * * http://stackoverflow.com/questions/1026069/capitalize-the-first-letter-of-string-in-javascript */ -UI.registerHelper('capitalizeFirstLetter', function (context) { +Blaze.registerHelper('capitalizeFirstLetter', function (context) { if (!context) { return; } return context.charAt(0).toUpperCase() + context.slice(1); -}); \ No newline at end of file +}); diff --git a/Packages/ohif-viewerbase/client/lib/helpers/formatDA.js b/Packages/ohif-viewerbase/client/lib/helpers/formatDA.js index 8c549d4f4..4f36a6a88 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/formatDA.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/formatDA.js @@ -1,7 +1,11 @@ +import { Blaze } from 'meteor/blaze'; +import { moment } from 'meteor/momentjs:moment'; + /** * A global Blaze UI helper function to format DICOM Dates using the Moment library */ -UI.registerHelper('formatDA', function (context, format, options) { + +const formatDA = (context, format, options) => { if (!context) { return undefined; } @@ -11,4 +15,8 @@ UI.registerHelper('formatDA', function (context, format, options) { strFormat = format; } return dateAsMoment.format(strFormat); -}); \ No newline at end of file +}; + +Blaze.registerHelper('formatDA', formatDA); + +export { formatDA }; diff --git a/Packages/ohif-viewerbase/client/lib/helpers/formatJSDate.js b/Packages/ohif-viewerbase/client/lib/helpers/formatJSDate.js index cdf015300..e8fb9cc84 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/formatJSDate.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/formatJSDate.js @@ -1,7 +1,10 @@ +import { Blaze } from 'meteor/blaze'; +import { moment } from 'meteor/momentjs:moment'; + /** * A global Blaze UI helper function to format JavaScript Dates using the Moment library */ -UI.registerHelper('formatJSDate', function(context, format, options) { +Blaze.registerHelper('formatJSDate', function(context, format, options) { if (!context) { return; } diff --git a/Packages/ohif-viewerbase/client/lib/helpers/formatNumberPrecision.js b/Packages/ohif-viewerbase/client/lib/helpers/formatNumberPrecision.js index a2571b52e..80e19f5eb 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/formatNumberPrecision.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/formatNumberPrecision.js @@ -1,8 +1,10 @@ +import { Blaze } from 'meteor/blaze'; + /** * A global Blaze UI helper to format a float value to a specified precision */ -UI.registerHelper('formatNumberPrecision', function(context, precision) { +Blaze.registerHelper('formatNumberPrecision', function(context, precision) { if (context != null) { return parseFloat(context).toFixed(precision); } -}); \ No newline at end of file +}); diff --git a/Packages/ohif-viewerbase/client/lib/helpers/formatPN.js b/Packages/ohif-viewerbase/client/lib/helpers/formatPN.js index 18b90ba7b..3bbd1ac81 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/formatPN.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/formatPN.js @@ -1,7 +1,8 @@ +import { Blaze } from 'meteor/blaze'; /** * Formats a patient name for display purposes */ -formatPN = function(context) { +const formatPN = context => { if (!context) { return; } @@ -11,4 +12,6 @@ formatPN = function(context) { /** * A global Blaze UI helper to format a patient name for display purposes */ -UI.registerHelper('formatPN', formatPN); \ No newline at end of file +Blaze.registerHelper('formatPN', formatPN); + +export { formatPN }; diff --git a/Packages/ohif-viewerbase/client/lib/helpers/formatTM.js b/Packages/ohif-viewerbase/client/lib/helpers/formatTM.js index f45f39c13..3c11ee297 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/formatTM.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/formatTM.js @@ -1,7 +1,11 @@ +import { Blaze } from 'meteor/blaze'; +import { moment } from 'meteor/momentjs:moment'; + /** * A global Blaze UI helper to format a DICOM Time for display using the Moment library */ -UI.registerHelper('formatTM', function(context, options) { + +const formatTM = (context, options) => { if (!context) { return; } @@ -21,4 +25,8 @@ UI.registerHelper('formatTM', function(context, options) { } return dateTime.format(format); -}); +}; + +Blaze.registerHelper('formatTM', formatTM); + +export { formatTM }; diff --git a/Packages/ohif-viewerbase/client/lib/helpers/getUsername.js b/Packages/ohif-viewerbase/client/lib/helpers/getUsername.js index eb3d7ef3f..6140398c6 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/getUsername.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/getUsername.js @@ -1,7 +1,10 @@ +import { Meteor } from 'meteor/meteor'; +import { Blaze } from 'meteor/blaze'; + /** * Helper for retrieving username given userId */ -UI.registerHelper('getUsername', function(userId) { +Blaze.registerHelper('getUsername', function(userId) { var user = Meteor.users.findOne({ userId: userId }); diff --git a/Packages/ohif-viewerbase/client/lib/helpers/ifTypeIs.js b/Packages/ohif-viewerbase/client/lib/helpers/ifTypeIs.js index 9ad1cbb5c..8637da5e8 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/ifTypeIs.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/ifTypeIs.js @@ -1,7 +1,9 @@ +import { Blaze } from 'meteor/blaze'; + /** * Helper for checking datatype of a variable */ -UI.registerHelper('ifTypeIs', function(value, match, attributeName) { +Blaze.registerHelper('ifTypeIs', function(value, match, attributeName) { if (typeof(value) === match) { return attributeName; } diff --git a/Packages/ohif-viewerbase/client/lib/helpers/inc.js b/Packages/ohif-viewerbase/client/lib/helpers/inc.js index e6238ad2b..00330094b 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/inc.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/inc.js @@ -1,3 +1,5 @@ -UI.registerHelper('inc', function(value) { +import { Blaze } from 'meteor/blaze'; + +Blaze.registerHelper('inc', function(value) { return parseInt(value) + 1; }); diff --git a/Packages/ohif-viewerbase/client/lib/helpers/isDisplaySetActive.js b/Packages/ohif-viewerbase/client/lib/helpers/isDisplaySetActive.js index 6f9050e4f..6b2e9af44 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/isDisplaySetActive.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/isDisplaySetActive.js @@ -1,3 +1,11 @@ +import { Template } from 'meteor/templating'; +import { Session } from 'meteor/session'; +import { $ } from 'meteor/jquery'; +import { viewportUtils } from '../viewportUtils'; + +import { OHIF } from 'meteor/ohif:core'; +import 'meteor/ohif:viewerbase'; + /** * Boolean helper to identify if a series instance is active in some viewport */ @@ -6,7 +14,7 @@ Template.registerHelper('isDisplaySetActive', (displaySetInstanceUid, viewportIn Session.get('LayoutManagerUpdated'); // Stop here if layoutManager is not defined yet - if (!window.layoutManager) { + if (!OHIF.viewerbase.layoutManager) { return; } @@ -22,22 +30,28 @@ Template.registerHelper('isDisplaySetActive', (displaySetInstanceUid, viewportIn if (_.isUndefined(viewportIndex)) { // Get the number of viewports that are currently displayed // (Note, viewportData may have more entries!) - const currentNumberOfViewports = layoutManager.getNumberOfViewports(); + const currentNumberOfViewports = OHIF.viewerbase.layoutManager.getNumberOfViewports(); + + // Get active viewport element + const activeViewport = viewportUtils.getActiveViewportElement(); + + // Get viewports elements + const viewportsElement = $('.imageViewerViewport'); // Loop through the viewport data up until the currently displayed // number of viewports - let viewportData = window.layoutManager.viewportData; + const viewportData = OHIF.viewerbase.layoutManager.viewportData; for (let i = 0; i < currentNumberOfViewports; i++) { const data = viewportData[i]; - // If the display set is displayed in this viewport, stop here - if (data && data.displaySetInstanceUid === displaySetInstanceUid) { + // If the display set is displayed in this viewport and is active, stop here + if (data && data.displaySetInstanceUid === displaySetInstanceUid && viewportsElement.get(data.viewportIndex) === activeViewport) { result = true; break; } } } else { - const data = window.layoutManager.viewportData[viewportIndex]; + const data = OHIF.viewerbase.layoutManager.viewportData[viewportIndex]; // If the display set is displayed in this viewport, stop here if (data && data.displaySetInstanceUid === displaySetInstanceUid) { diff --git a/Packages/ohif-viewerbase/client/lib/helpers/isTouchDevice.js b/Packages/ohif-viewerbase/client/lib/helpers/isTouchDevice.js index 81d5476ee..4711f6137 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/isTouchDevice.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/isTouchDevice.js @@ -1,3 +1,5 @@ +import { Blaze } from 'meteor/blaze'; + /** * Helper function to determine if the current client devices * is touch-capable. This can be used to modify certain aspects of the UI. @@ -6,7 +8,7 @@ * * @returns {boolean} true if the client device is touch-capable, false otherwise */ -isTouchDevice = function() { +const isTouchDevice = () => { return (('ontouchstart' in window) || (navigator.MaxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)); @@ -17,4 +19,6 @@ isTouchDevice = function() { * * @returns {boolean} true if the client device is touch-capable, false otherwise */ -UI.registerHelper('isTouchDevice', isTouchDevice); +Blaze.registerHelper('isTouchDevice', isTouchDevice); + +export { isTouchDevice }; diff --git a/Packages/ohif-viewerbase/client/lib/helpers/jsDateFromNow.js b/Packages/ohif-viewerbase/client/lib/helpers/jsDateFromNow.js index ff71bb97d..c5dac044d 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/jsDateFromNow.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/jsDateFromNow.js @@ -1,7 +1,11 @@ +import { Blaze } from 'meteor/blaze'; +import { Session } from 'meteor/session'; +import { moment } from 'meteor/momentjs:moment'; + /** * A global Blaze UI helper function to format JavaScript Dates using the Moment library */ -UI.registerHelper('jsDateFromNow', function(context, format, options) { +Blaze.registerHelper('jsDateFromNow', function(context, format, options) { if (!context) { return; } diff --git a/Packages/ohif-viewerbase/client/lib/helpers/objectEach.js b/Packages/ohif-viewerbase/client/lib/helpers/objectEach.js index 8629d50b7..a29185307 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/objectEach.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/objectEach.js @@ -1,4 +1,7 @@ -UI.registerHelper('objectEach', function(object) { +import { Blaze } from 'meteor/blaze'; +import { _ } from 'meteor/underscore'; + +Blaze.registerHelper('objectEach', function(object) { // http://stackoverflow.com/questions/30234732/how-to-print-key-and-values-in-meteor-template return _.map(object, function(value, key) { return _.extend({key: key}, value); diff --git a/Packages/ohif-viewerbase/client/lib/helpers/objectToPairs.js b/Packages/ohif-viewerbase/client/lib/helpers/objectToPairs.js index 348ae0d91..ff71343f0 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/objectToPairs.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/objectToPairs.js @@ -1,4 +1,7 @@ -UI.registerHelper('objectToPairs', function(object) { +import { Blaze } from 'meteor/blaze'; +import { _ } from 'meteor/underscore'; + +Blaze.registerHelper('objectToPairs', function(object) { // http://stackoverflow.com/questions/30234732/how-to-print-key-and-values-in-meteor-template return _.map(object, function(value, key) { return { diff --git a/Packages/ohif-viewerbase/client/lib/helpers/prettyPrintStringify.js b/Packages/ohif-viewerbase/client/lib/helpers/prettyPrintStringify.js index 2e4a44759..b2a65229b 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/prettyPrintStringify.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/prettyPrintStringify.js @@ -1,3 +1,5 @@ +import { Blaze } from 'meteor/blaze'; + /** * A global Blaze UI helper to Stringify a JavaScript object * @@ -5,7 +7,7 @@ * * http://stackoverflow.com/questions/1026069/capitalize-the-first-letter-of-string-in-javascript */ -UI.registerHelper('prettyPrintStringify', function(context) { +Blaze.registerHelper('prettyPrintStringify', function(context) { if (!context) { return; } diff --git a/Packages/ohif-viewerbase/client/lib/helpers/sorting.js b/Packages/ohif-viewerbase/client/lib/helpers/sorting.js index d7374cae3..fbd3ca62e 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/sorting.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/sorting.js @@ -1,5 +1,5 @@ -import { _ } from 'meteor/underscore'; import { Template } from 'meteor/templating'; +import { _ } from 'meteor/underscore'; /** * Global Blaze UI helper to sort array elements @@ -54,4 +54,4 @@ function getKeyValue(object, keyPath) { } } return object; -} \ No newline at end of file +} diff --git a/Packages/ohif-viewerbase/client/lib/helpers/studyThumbnails.js b/Packages/ohif-viewerbase/client/lib/helpers/studyThumbnails.js index 5c2a4693f..09fe37110 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/studyThumbnails.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/studyThumbnails.js @@ -1,3 +1,6 @@ +import { Template } from 'meteor/templating'; +import { _ } from 'meteor/underscore'; + /** * A global Blaze UI helper to get the thumbnails for the given study */ diff --git a/Packages/ohif-viewerbase/client/lib/instanceClassSpecificViewport.js b/Packages/ohif-viewerbase/client/lib/instanceClassSpecificViewport.js index aa5874147..febd314f4 100644 --- a/Packages/ohif-viewerbase/client/lib/instanceClassSpecificViewport.js +++ b/Packages/ohif-viewerbase/client/lib/instanceClassSpecificViewport.js @@ -1,6 +1,8 @@ -var instanceClassViewportSettingsFunctions = {}; -getInstanceClassDefaultViewport = function(series, enabledElement, imageId) { - var instanceClass = series.sopClassUid; + +const instanceClassViewportSettingsFunctions = {}; + +const getInstanceClassDefaultViewport = (series, enabledElement, imageId) => { + let instanceClass = series.sopClassUid; if (!instanceClassViewportSettingsFunctions[instanceClass]) { return; @@ -9,6 +11,8 @@ getInstanceClassDefaultViewport = function(series, enabledElement, imageId) { return instanceClassViewportSettingsFunctions[instanceClass](series, enabledElement, imageId); }; -setInstanceClassDefaultViewportFunction = function(instanceClass, fn) { +const setInstanceClassDefaultViewportFunction = (instanceClass, fn) => { instanceClassViewportSettingsFunctions[instanceClass] = fn; -}; \ No newline at end of file +}; + +export { getInstanceClassDefaultViewport, setInstanceClassDefaultViewportFunction }; diff --git a/Packages/ohif-viewerbase/client/lib/isImage.js b/Packages/ohif-viewerbase/client/lib/isImage.js index 55238e272..f90d26ce5 100644 --- a/Packages/ohif-viewerbase/client/lib/isImage.js +++ b/Packages/ohif-viewerbase/client/lib/isImage.js @@ -1,59 +1,61 @@ +import { sopClassDictionary } from './sopClassDictionary'; + /** * Checks whether dicom files with specified SOP Class UID have image data * @param {string} sopClassUid - SOP Class UID to be checked * @returns {boolean} - true if it has image data */ -isImage = function(sopClassUid) { - if (sopClassUid == sopClassDictionary.ComputedRadiographyImageStorage - || sopClassUid == sopClassDictionary.DigitalXRayImageStorageForPresentation - || sopClassUid == sopClassDictionary.DigitalXRayImageStorageForProcessing - || sopClassUid == sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation - || sopClassUid == sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing - || sopClassUid == sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation - || sopClassUid == sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing - || sopClassUid == sopClassDictionary.CTImageStorage - || sopClassUid == sopClassDictionary.EnhancedCTImageStorage - || sopClassUid == sopClassDictionary.LegacyConvertedEnhancedCTImageStorage - || sopClassUid == sopClassDictionary.UltrasoundMultiframeImageStorage - || sopClassUid == sopClassDictionary.MRImageStorage - || sopClassUid == sopClassDictionary.EnhancedMRImageStorage - || sopClassUid == sopClassDictionary.EnhancedMRColorImageStorage - || sopClassUid == sopClassDictionary.LegacyConvertedEnhancedMRImageStorage - || sopClassUid == sopClassDictionary.UltrasoundImageStorage - || sopClassUid == sopClassDictionary.SecondaryCaptureImageStorage - || sopClassUid == sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage - || sopClassUid == sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage - || sopClassUid == sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage - || sopClassUid == sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage - || sopClassUid == sopClassDictionary.XRayAngiographicImageStorage - || sopClassUid == sopClassDictionary.EnhancedXAImageStorage - || sopClassUid == sopClassDictionary.XRayRadiofluoroscopicImageStorage - || sopClassUid == sopClassDictionary.EnhancedXRFImageStorage - || sopClassUid == sopClassDictionary.XRay3DAngiographicImageStorage - || sopClassUid == sopClassDictionary.XRay3DCraniofacialImageStorage - || sopClassUid == sopClassDictionary.BreastTomosynthesisImageStorage - || sopClassUid == sopClassDictionary.BreastProjectionXRayImageStorageForPresentation - || sopClassUid == sopClassDictionary.BreastProjectionXRayImageStorageForProcessing - || sopClassUid == sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation - || sopClassUid == sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing - || sopClassUid == sopClassDictionary.NuclearMedicineImageStorage - || sopClassUid == sopClassDictionary.VLEndoscopicImageStorage - || sopClassUid == sopClassDictionary.VideoEndoscopicImageStorage - || sopClassUid == sopClassDictionary.VLMicroscopicImageStorage - || sopClassUid == sopClassDictionary.VideoMicroscopicImageStorage - || sopClassUid == sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage - || sopClassUid == sopClassDictionary.VLPhotographicImageStorage - || sopClassUid == sopClassDictionary.VideoPhotographicImageStorage - || sopClassUid == sopClassDictionary.OphthalmicPhotography8BitImageStorage - || sopClassUid == sopClassDictionary.OphthalmicPhotography16BitImageStorage - || sopClassUid == sopClassDictionary.OphthalmicTomographyImageStorage - || sopClassUid == sopClassDictionary.VLWholeSlideMicroscopyImageStorage - || sopClassUid == sopClassDictionary.PositronEmissionTomographyImageStorage - || sopClassUid == sopClassDictionary.EnhancedPETImageStorage - || sopClassUid == sopClassDictionary.LegacyConvertedEnhancedPETImageStorage - || sopClassUid == sopClassDictionary.RTImageStorage) { +export function isImage(sopClassUid) { + if (sopClassUid === sopClassDictionary.ComputedRadiographyImageStorage + || sopClassUid === sopClassDictionary.DigitalXRayImageStorageForPresentation + || sopClassUid === sopClassDictionary.DigitalXRayImageStorageForProcessing + || sopClassUid === sopClassDictionary.DigitalMammographyXRayImageStorageForPresentation + || sopClassUid === sopClassDictionary.DigitalMammographyXRayImageStorageForProcessing + || sopClassUid === sopClassDictionary.DigitalIntraOralXRayImageStorageForPresentation + || sopClassUid === sopClassDictionary.DigitalIntraOralXRayImageStorageForProcessing + || sopClassUid === sopClassDictionary.CTImageStorage + || sopClassUid === sopClassDictionary.EnhancedCTImageStorage + || sopClassUid === sopClassDictionary.LegacyConvertedEnhancedCTImageStorage + || sopClassUid === sopClassDictionary.UltrasoundMultiframeImageStorage + || sopClassUid === sopClassDictionary.MRImageStorage + || sopClassUid === sopClassDictionary.EnhancedMRImageStorage + || sopClassUid === sopClassDictionary.EnhancedMRColorImageStorage + || sopClassUid === sopClassDictionary.LegacyConvertedEnhancedMRImageStorage + || sopClassUid === sopClassDictionary.UltrasoundImageStorage + || sopClassUid === sopClassDictionary.SecondaryCaptureImageStorage + || sopClassUid === sopClassDictionary.MultiframeSingleBitSecondaryCaptureImageStorage + || sopClassUid === sopClassDictionary.MultiframeGrayscaleByteSecondaryCaptureImageStorage + || sopClassUid === sopClassDictionary.MultiframeGrayscaleWordSecondaryCaptureImageStorage + || sopClassUid === sopClassDictionary.MultiframeTrueColorSecondaryCaptureImageStorage + || sopClassUid === sopClassDictionary.XRayAngiographicImageStorage + || sopClassUid === sopClassDictionary.EnhancedXAImageStorage + || sopClassUid === sopClassDictionary.XRayRadiofluoroscopicImageStorage + || sopClassUid === sopClassDictionary.EnhancedXRFImageStorage + || sopClassUid === sopClassDictionary.XRay3DAngiographicImageStorage + || sopClassUid === sopClassDictionary.XRay3DCraniofacialImageStorage + || sopClassUid === sopClassDictionary.BreastTomosynthesisImageStorage + || sopClassUid === sopClassDictionary.BreastProjectionXRayImageStorageForPresentation + || sopClassUid === sopClassDictionary.BreastProjectionXRayImageStorageForProcessing + || sopClassUid === sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForPresentation + || sopClassUid === sopClassDictionary.IntravascularOpticalCoherenceTomographyImageStorageForProcessing + || sopClassUid === sopClassDictionary.NuclearMedicineImageStorage + || sopClassUid === sopClassDictionary.VLEndoscopicImageStorage + || sopClassUid === sopClassDictionary.VideoEndoscopicImageStorage + || sopClassUid === sopClassDictionary.VLMicroscopicImageStorage + || sopClassUid === sopClassDictionary.VideoMicroscopicImageStorage + || sopClassUid === sopClassDictionary.VLSlideCoordinatesMicroscopicImageStorage + || sopClassUid === sopClassDictionary.VLPhotographicImageStorage + || sopClassUid === sopClassDictionary.VideoPhotographicImageStorage + || sopClassUid === sopClassDictionary.OphthalmicPhotography8BitImageStorage + || sopClassUid === sopClassDictionary.OphthalmicPhotography16BitImageStorage + || sopClassUid === sopClassDictionary.OphthalmicTomographyImageStorage + || sopClassUid === sopClassDictionary.VLWholeSlideMicroscopyImageStorage + || sopClassUid === sopClassDictionary.PositronEmissionTomographyImageStorage + || sopClassUid === sopClassDictionary.EnhancedPETImageStorage + || sopClassUid === sopClassDictionary.LegacyConvertedEnhancedPETImageStorage + || sopClassUid === sopClassDictionary.RTImageStorage) { return true; } return false; -}; \ No newline at end of file +} diff --git a/Packages/ohif-viewerbase/client/lib/metaDataProvider.js b/Packages/ohif-viewerbase/client/lib/metaDataProvider.js index 9d2c457a9..2bf4baa57 100644 --- a/Packages/ohif-viewerbase/client/lib/metaDataProvider.js +++ b/Packages/ohif-viewerbase/client/lib/metaDataProvider.js @@ -1,6 +1,7 @@ -import {parsingUtils} from './parsingUtils' +import { Meteor } from 'meteor/meteor'; +import { parsingUtils } from './parsingUtils'; -var metaDataLookup = {}; +const metaDataLookup = {}; /** * Cornerstone MetaData provider to store image meta data @@ -14,14 +15,14 @@ var metaDataLookup = {}; * @param {String} imageId The Cornerstone ImageId * @param {Object} data An object containing instance, series, and study metaData */ -addMetaData = function(imageId, data) { - var instanceMetaData = data.instance; - var seriesMetaData = data.series; - var studyMetaData = data.study; - var imageIndex = data.imageIndex; - var numImages = data.numImages; +const addMetaData = (imageId, data) => { + let instanceMetaData = data.instance; + let seriesMetaData = data.series; + let studyMetaData = data.study; + let imageIndex = data.imageIndex; + let numImages = data.numImages; - var metaData = {}; + let metaData = {}; metaData.study = { patientId: studyMetaData.patientId, @@ -66,8 +67,8 @@ addMetaData = function(imageId, data) { * @param type (e.g. series, instance, tagDisplay) * @param data */ -addSpecificMetadata = function(imageId, type, data) { - var metaData = {}; +const addSpecificMetadata = (imageId, type, data) => { + let metaData = {}; metaData[type] = data; metaDataLookup[imageId] = $.extend(metaDataLookup[imageId], metaData); @@ -92,8 +93,8 @@ function getFromDataSet(dataSet, type, tag) { * * @param image */ -updateMetaData = function(image) { - var imageMetaData = metaDataLookup[image.imageId]; +const updateMetaData = image => { + const imageMetaData = metaDataLookup[image.imageId]; if (!imageMetaData) { return; } @@ -141,13 +142,13 @@ function getImagePlane(instance) { return; } - var imageOrientation = instance.imageOrientationPatient.split('\\'); - var imagePosition = instance.imagePositionPatient.split('\\'); + let imageOrientation = instance.imageOrientationPatient.split('\\'); + let imagePosition = instance.imagePositionPatient.split('\\'); - var columnPixelSpacing = 1.0; - var rowPixelSpacing = 1.0; + let columnPixelSpacing = 1.0; + let rowPixelSpacing = 1.0; if (instance.pixelSpacing) { - var split = instance.pixelSpacing.split('\\'); + let split = instance.pixelSpacing.split('\\'); rowPixelSpacing = parseFloat(split[0]); columnPixelSpacing = parseFloat(split[1]); } @@ -178,9 +179,9 @@ function getImagePlane(instance) { * @param dataSet {Object} An instance of dicomParser.DataSet object where multiframe information can be found. * @return {Object} An object containing multiframe image metadata (frameIncrementPointer, frameTime, frameTimeVector, etc). */ -getMultiframeModuleMetaData = function(dataSet) { +function getMultiframeModuleMetaData(dataSet) { - var numberOfFrames, + let numberOfFrames, frameIncrementPointer, frameTime, frameTimeVector, @@ -232,7 +233,7 @@ getMultiframeModuleMetaData = function(dataSet) { } return imageInfo; -}; +} /** * Looks up metaData for Cornerstone Tools given a specified type and imageId @@ -244,7 +245,7 @@ getMultiframeModuleMetaData = function(dataSet) { * @returns {Object} Relevant metaData of the specified type */ function provider(type, imageId) { - var imageMetaData = metaDataLookup[imageId]; + let imageMetaData = metaDataLookup[imageId]; if (!imageMetaData) { return; } @@ -257,3 +258,9 @@ function provider(type, imageId) { Meteor.startup(function() { cornerstoneTools.metaData.addProvider(provider); }); + +/** + * Export relevant symbols + */ + +export { addMetaData, addSpecificMetadata, updateMetaData }; diff --git a/Packages/ohif-viewerbase/client/lib/setActiveViewport.js b/Packages/ohif-viewerbase/client/lib/setActiveViewport.js index fa6ac2774..dffe5f151 100644 --- a/Packages/ohif-viewerbase/client/lib/setActiveViewport.js +++ b/Packages/ohif-viewerbase/client/lib/setActiveViewport.js @@ -1,14 +1,32 @@ +import { Session } from 'meteor/session'; +import { $ } from 'meteor/jquery'; import { OHIF } from 'meteor/ohif:core'; +import { enablePrefetchOnElement } from './enablePrefetchOnElement'; +import { displayReferenceLines } from './displayReferenceLines'; -setActiveViewport = element => { +/** + * Sets a viewport element active + * @param {node} element DOM element to be activated + */ +export function setActiveViewport(element) { if (!element) { + OHIF.log.info('setActiveViewport element does not exist'); return; } - const viewportIndex = $('.imageViewerViewport').index(element); + const viewerports = $('.imageViewerViewport'); + const viewportIndex = viewerports.index(element); const jQueryElement = $(element); - // When an ActivateViewport event is fired, update the Meteor Session + OHIF.log.info(`setActiveViewport setting viewport index: ${viewportIndex}`); + + // If viewport is not active + if(!jQueryElement.parents('.viewportContainer').hasClass('active')) { + // Trigger an event for compatibility with other systems + jQueryElement.trigger('OHIFBeforeActivateViewport'); + } + + // When an OHIFActivateViewport event is fired, update the Meteor Session // with the viewport index that it was fired from. Session.set('activeViewport', viewportIndex); @@ -28,11 +46,19 @@ setActiveViewport = element => { const domElement = jQueryElement.get(0); enablePrefetchOnElement(domElement); displayReferenceLines(domElement); - OHIF.viewer.stackImagePositionOffsetSynchronizer.update(); + // @TODO Add this to OHIFAfterActivateViewport handler... + if (OHIF.viewer.stackImagePositionOffsetSynchronizer) { + OHIF.viewer.stackImagePositionOffsetSynchronizer.update(); + } } // Set the div to focused, so keypress events are handled //$(element).focus(); //.focus() event breaks in FF&IE jQueryElement.triggerHandler('focus'); -}; + + // Trigger OHIFAfterActivateViewport event on activated instance + // for compatibility with other systems + jQueryElement.trigger('OHIFAfterActivateViewport'); + +} diff --git a/Packages/ohif-viewerbase/client/lib/setFocusToActiveViewport.js b/Packages/ohif-viewerbase/client/lib/setFocusToActiveViewport.js index 98ebfca87..ff27a270c 100644 --- a/Packages/ohif-viewerbase/client/lib/setFocusToActiveViewport.js +++ b/Packages/ohif-viewerbase/client/lib/setFocusToActiveViewport.js @@ -1,19 +1,24 @@ +import { Session } from 'meteor/session'; +import { $ } from 'meteor/jquery'; + /** * Restores the browser focus to the currently specified active viewport * as determined from Meteor's Session variable. * - * This is allows keydown events to be captured on the focused element + * This is allows keydown events to be captured on the focused element. */ -setFocusToActiveViewport = function() { +const setFocusToActiveViewport = () => { // Get the list of viewports - var viewports = $('.imageViewerViewport'); + const viewports = $('.imageViewerViewport'); // Get the current active viewport index from Session - var activeViewportIndex = Session.get('activeViewport'); + const activeViewportIndex = Session.get('activeViewport'); // Find the div from the list of viewports - var activeViewport = viewports.eq(activeViewportIndex); + const activeViewport = viewports.eq(activeViewportIndex); // Set the browser focus to this div activeViewport.focus(); -}; \ No newline at end of file +}; + +export { setFocusToActiveViewport }; \ No newline at end of file diff --git a/Packages/ohif-viewerbase/client/lib/setMammogramViewportAlignment.js b/Packages/ohif-viewerbase/client/lib/setMammogramViewportAlignment.js index 0ccd1cc31..c19c80dfe 100644 --- a/Packages/ohif-viewerbase/client/lib/setMammogramViewportAlignment.js +++ b/Packages/ohif-viewerbase/client/lib/setMammogramViewportAlignment.js @@ -1,19 +1,24 @@ -setMammogramViewportAlignment = function(series, enabledElement, imageId) { - // Don't apply the MG viewport alignment to other series types - var viewTypes = ['MLO', 'CC', 'LM', 'ML', 'XCCL']; - var requiresInversion = ['LM']; // Tomo series are flipped +import { Meteor } from 'meteor/meteor'; +import { $ } from 'meteor/jquery'; +import { setInstanceClassDefaultViewportFunction } from './instanceClassSpecificViewport'; +import { addSpecificMetadata } from './metaDataProvider'; - var instance = cornerstoneTools.metaData.get('instance', imageId); +const setMammogramViewportAlignment = (series, enabledElement, imageId) => { + // Don't apply the MG viewport alignment to other series types + let viewTypes = ['MLO', 'CC', 'LM', 'ML', 'XCCL']; + let requiresInversion = ['LM']; // Tomo series are flipped + + let instance = cornerstoneTools.metaData.get('instance', imageId); if (!instance) { return; } - var laterality = instance.laterality; - var element = enabledElement.element; - var position; + let laterality = instance.laterality; + let element = enabledElement.element; + let position; - var left = $(enabledElement.canvas).offset().left; - var right = left + enabledElement.canvas.width; + let left = $(enabledElement.canvas).offset().left; + let right = left + enabledElement.canvas.width; if (viewTypes.indexOf(instance.viewPosition) < 0) { return; @@ -58,4 +63,6 @@ setMammogramViewportAlignment = function(series, enabledElement, imageId) { Meteor.startup(function() { setInstanceClassDefaultViewportFunction("1.2.840.10008.5.1.4.1.1.1.2", setMammogramViewportAlignment); -}); \ No newline at end of file +}); + +export { setMammogramViewportAlignment }; diff --git a/Packages/ohif-viewerbase/client/lib/sopClassDictionary.js b/Packages/ohif-viewerbase/client/lib/sopClassDictionary.js index 3d0ab24c0..db851d0b5 100644 --- a/Packages/ohif-viewerbase/client/lib/sopClassDictionary.js +++ b/Packages/ohif-viewerbase/client/lib/sopClassDictionary.js @@ -1,4 +1,5 @@ -sopClassDictionary = { + +export const sopClassDictionary = { ComputedRadiographyImageStorage: "1.2.840.10008.5.1.4.1.1.1", DigitalXRayImageStorageForPresentation: "1.2.840.10008.5.1.4.1.1.1.1", DigitalXRayImageStorageForProcessing: "1.2.840.10008.5.1.4.1.1.1.1.1", @@ -112,4 +113,4 @@ sopClassDictionary = { GenericImplantTemplateStorage: "1.2.840.10008.5.1.4.43.1", ImplantAssemblyTemplateStorage: "1.2.840.10008.5.1.4.44.1", ImplantTemplateGroupStorage: "1.2.840.10008.5.1.4.45.1" -}; \ No newline at end of file +}; diff --git a/Packages/ohif-viewerbase/client/lib/sortStudy.js b/Packages/ohif-viewerbase/client/lib/sortStudy.js index a03d37b86..cc928798e 100644 --- a/Packages/ohif-viewerbase/client/lib/sortStudy.js +++ b/Packages/ohif-viewerbase/client/lib/sortStudy.js @@ -1,21 +1,19 @@ +import { OHIFError } from './classes/OHIFError'; + /** * Sorts the series and instances inside a study instance by their series * and instance numbers in ascending order. * * @param {Object} study The study instance */ -sortStudy = function(study) { +export function sortStudy(study) { if (!study || !study.seriesList) { - throw "Insufficient study data was provided to sortStudy"; + throw new OHIFError('Insufficient study data was provided to sortStudy'); } - - study.seriesList.sort(function(a, b) { - return a.seriesNumber - b.seriesNumber; - }); - study.seriesList.forEach(function(series){ - series.instances.sort(function(a, b) { - return a.instanceNumber - b.instanceNumber; - }); + study.seriesList.sort((a, b) => a.seriesNumber - b.seriesNumber); + + study.seriesList.forEach(series => { + series.instances.sort((a, b) => a.instanceNumber - b.instanceNumber); }); -}; \ No newline at end of file +} diff --git a/Packages/ohif-viewerbase/client/lib/switchToImageByIndex.js b/Packages/ohif-viewerbase/client/lib/switchToImageByIndex.js index 4ecd232b3..753b3519d 100644 --- a/Packages/ohif-viewerbase/client/lib/switchToImageByIndex.js +++ b/Packages/ohif-viewerbase/client/lib/switchToImageByIndex.js @@ -1,3 +1,5 @@ +import { viewportUtils } from './viewportUtils'; + /** * This function switches to an image given an element and the index of the image in the current stack * Note: Negative indexing is supported: @@ -7,7 +9,7 @@ * @param element * @param {number} [newImageIdIndex] The image index in the stack to switch to. */ -switchToImageByIndex = function(newImageIdIndex) { - var element = getActiveViewportElement(); +export function switchToImageByIndex(newImageIdIndex) { + var element = viewportUtils.getActiveViewportElement(); cornerstoneTools.scrollToIndex(element, newImageIdIndex); -}; +} diff --git a/Packages/ohif-viewerbase/client/lib/switchToImageRelative.js b/Packages/ohif-viewerbase/client/lib/switchToImageRelative.js index 5d1c3f636..92e4845cb 100644 --- a/Packages/ohif-viewerbase/client/lib/switchToImageRelative.js +++ b/Packages/ohif-viewerbase/client/lib/switchToImageRelative.js @@ -1,3 +1,5 @@ +import { viewportUtils } from './viewportUtils'; + /** * This function switches to an image given an element and * the relative distance from the current image in the stack @@ -7,7 +9,7 @@ * @param element * @param {number} [distanceFromCurrentIndex] The image index in the stack to switch to. */ -switchToImageRelative = function(distanceFromCurrentIndex) { - var element = getActiveViewportElement(); +export function switchToImageRelative(distanceFromCurrentIndex) { + var element = viewportUtils.getActiveViewportElement(); cornerstoneTools.scroll(element, distanceFromCurrentIndex); -}; +} diff --git a/Packages/ohif-viewerbase/client/lib/updateAllViewports.js b/Packages/ohif-viewerbase/client/lib/updateAllViewports.js index 8137550a9..c0afe2179 100644 --- a/Packages/ohif-viewerbase/client/lib/updateAllViewports.js +++ b/Packages/ohif-viewerbase/client/lib/updateAllViewports.js @@ -1,6 +1,8 @@ -updateAllViewports = function() { +import { $ } from 'meteor/jquery'; + +export function updateAllViewports() { var viewports = $('.imageViewerViewport').not('.empty'); viewports.each(function(index, element) { cornerstone.updateImage(element); }); -}; \ No newline at end of file +} diff --git a/Packages/ohif-viewerbase/client/lib/viewportUtils.js b/Packages/ohif-viewerbase/client/lib/viewportUtils.js index a6c9abeea..c47bbf548 100644 --- a/Packages/ohif-viewerbase/client/lib/viewportUtils.js +++ b/Packages/ohif-viewerbase/client/lib/viewportUtils.js @@ -3,6 +3,7 @@ import { Random } from 'meteor/random'; import { $ } from 'meteor/jquery'; import { _ } from 'meteor/underscore'; // Local Modules +import { OHIF } from 'meteor/ohif:core'; import { updateOrientationMarkers } from './updateOrientationMarkers'; import { getInstanceClassDefaultViewport } from './instanceClassSpecificViewport'; @@ -120,6 +121,16 @@ function clearTools() { cornerstone.updateImage(element); } +function linkStackScroll() { + const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer; + + if(synchronizer.isActive()) { + synchronizer.deactivate(); + } else { + synchronizer.activate(); + } +} + // This function was originally defined alone inside client/lib/toggleDialog.js // and has been moved here to avoid circular dependency issues. function toggleDialog(element) { @@ -238,6 +249,17 @@ function stopAllClips() { } +function isStackScrollLinkingDisabled() { + // Its called everytime active viewport and/or layout change + Session.get('activeViewport'); + Session.get('LayoutManagerUpdated'); + + const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer; + const linkableViewports = synchronizer.getLinkableViewports(); + + return linkableViewports.length <= 1; +} + // Create an event listener to update playing state when a clip stops playing $(window).on('CornerstoneToolsClipStopped', () => Session.set('UpdateCINE', Random.id())); @@ -257,12 +279,14 @@ const viewportUtils = { flipH, resetViewport, clearTools, + linkStackScroll, toggleDialog, toggleCinePlay, toggleCineDialog, isPlaying, hasMultipleFrames, - stopAllClips + stopAllClips, + isStackScrollLinkingDisabled }; export { viewportUtils }; diff --git a/Packages/ohif-viewerbase/package.js b/Packages/ohif-viewerbase/package.js index a39d9481e..a00505dec 100644 --- a/Packages/ohif-viewerbase/package.js +++ b/Packages/ohif-viewerbase/package.js @@ -16,9 +16,10 @@ Package.onUse(function(api) { api.use('validatejs'); // Our custom packages - api.use('design'); + api.use('ohif:design'); api.use('ohif:core'); api.use('ohif:cornerstone'); + api.use('ohif:log'); const assets = [ 'assets/icons.svg', @@ -137,7 +138,6 @@ Package.onUse(function(api) { api.addFiles('client/components/viewer/annotationDialogs/annotationDialogs.styl', 'client'); api.addFiles('client/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.html', 'client'); - api.addFiles('client/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js', 'client'); api.addFiles('client/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.styl', 'client'); api.addFiles('client/components/viewer/viewportOverlay/viewportOverlay.html', 'client'); @@ -178,6 +178,10 @@ Package.onUse(function(api) { api.addFiles('client/components/viewer/confirmDeleteDialog/confirmDeleteDialog.js', 'client'); api.addFiles('client/components/viewer/confirmDeleteDialog/confirmDeleteDialog.styl', 'client'); + api.addFiles('client/components/viewer/textMarkerDialogs/textMarkerDialogs.html', 'client'); + api.addFiles('client/components/viewer/textMarkerDialogs/textMarkerDialogs.js', 'client'); + api.addFiles('client/components/viewer/textMarkerDialogs/textMarkerDialogs.styl', 'client'); + api.addFiles('client/components/viewer/displaySetNavigation/displaySetNavigation.html', 'client'); api.addFiles('client/components/viewer/displaySetNavigation/displaySetNavigation.js', 'client'); @@ -197,105 +201,13 @@ Package.onUse(function(api) { api.addFiles('client/components/viewer/studyTimepointBrowser/studyTimepointStudy.styl', 'client'); api.addFiles('client/components/viewer/studyTimepointBrowser/studyTimepointStudy.js', 'client'); - // Library functions - api.addFiles('client/lib/layoutManager.js', 'client'); - api.addFiles('client/lib/createStacks.js', 'client'); - api.addFiles('client/lib/getImageId.js', 'client'); - api.addFiles('client/lib/getWADORSImageId.js', 'client'); - api.addFiles('client/lib/metaDataProvider.js', 'client'); - api.addFiles('client/lib/sortStudy.js', 'client'); - api.addFiles('client/lib/toolManager.js', 'client'); - api.addFiles('client/lib/enablePrefetchOnElement.js', 'client'); - api.addFiles('client/lib/displayReferenceLines.js', 'client'); - api.addFiles('client/lib/toggleDialog.js', 'client'); - api.addFiles('client/lib/setActiveViewport.js', 'client'); - api.addFiles('client/lib/switchToImageByIndex.js', 'client'); - api.addFiles('client/lib/switchToImageRelative.js', 'client'); - api.addFiles('client/lib/enableHotkeys.js', 'client'); - api.addFiles('client/lib/viewportFunctions.js', 'client'); - api.addFiles('client/lib/WLPresets.js', 'client'); - api.addFiles('client/lib/resizeViewportElements.js', 'client'); - api.addFiles('client/lib/setFocusToActiveViewport.js', 'client'); - api.addFiles('client/lib/updateAllViewports.js', 'client'); - api.addFiles('client/lib/stackImagePositionOffsetSynchronizer.js', 'client'); - - api.addFiles('client/lib/instanceClassSpecificViewport.js', 'client'); - api.addFiles('client/lib/setMammogramViewportAlignment.js', 'client'); - api.addFiles('client/lib/isImage.js', 'client'); - api.addFiles('client/lib/sopClassDictionary.js', 'client'); - api.addFiles('client/lib/debugReactivity.js', 'client'); - api.addFiles('client/lib/unloadHandlers.js', 'client'); - - api.export('resizeViewportElements', 'client'); - api.export('handleResize', 'client'); - api.export('enableHotkeys', 'client'); - api.export('enablePrefetchOnElement', 'client'); - api.export('displayReferenceLines', 'client'); - api.export('setActiveViewport', 'client'); - api.export('createStacks', 'client'); - api.export('getImageId', 'client'); - api.export('getWADORSImageId', 'client'); - api.export('metaDataProvider', 'client'); - api.export('sortStudy', 'client'); - api.export('updateOrientationMarkers', 'client'); - api.export('setFocusToActiveViewport', 'client'); - api.export('updateAllViewports', 'client'); - api.export('queryStudies', 'client'); - api.export('getNumberOfFilesInStudy', 'client'); - api.export('importStudies', 'client'); - api.export('getActiveViewportElement', 'client'); - api.export('getInstanceClassDefaultViewport', 'client'); - api.export('showConfirmDialog', 'client'); - api.export('applyWLPreset', 'client'); - api.export('toggleDialog', 'client'); - api.export('isImage', 'client'); - api.export('sopClassDictionary', 'client'); - api.export('addMetaData', 'client'); - api.export('hasMultipleFrames', 'client'); - api.export('isStackScrollLinkingDisabled') - - // Viewer management objects - api.export('toolManager', 'client'); - api.export('LayoutManager', 'client'); // Collections api.export('ViewerStudies', 'client'); - // UI Helpers - api.addFiles([ - 'client/lib/helpers/formatDA.js', - 'client/lib/helpers/formatJSDate.js', - 'client/lib/helpers/jsDateFromNow.js', - 'client/lib/helpers/formatNumberPrecision.js', - 'client/lib/helpers/formatTM.js', - 'client/lib/helpers/inc.js', - 'client/lib/helpers/isDisplaySetActive.js', - 'client/lib/helpers/getUsername.js', - 'client/lib/helpers/capitalizeFirstLetter.js', - 'client/lib/helpers/objectToPairs.js', - 'client/lib/helpers/objectEach.js', - 'client/lib/helpers/ifTypeIs.js', - 'client/lib/helpers/prettyPrintStringify.js', - 'client/lib/helpers/sorting.js', - 'client/lib/helpers/studyThumbnails.js', - 'client/lib/helpers/formatPN.js' - ], 'client'); - api.export('formatPN', 'client'); + api.export('dialogPolyfill', 'client'); - api.addFiles('client/lib/helpers/isTouchDevice.js', 'client'); - api.export('isTouchDevice', 'client'); + api.mainModule('main.js', 'client'); - // TODO: Clean this up later, no real need to export these - // Need to move functionList into viewerbase - api.export('toggleCineDialog', 'client'); - api.export('toggleCinePlay', 'client'); - api.export('clearTools', 'client'); - api.export('resetViewport', 'client'); - api.export('invert', 'client'); - api.export('flipV', 'client'); - api.export('flipH', 'client'); - api.export('rotateR', 'client'); - api.export('rotateL', 'client'); - api.export('link', 'client'); });