// Displays Series in Viewports given a Protocol and list of Studies LayoutManager = class LayoutManager { constructor(parentNode, studies) { log.info('LayoutManager'); this.parentNode = parentNode; this.studies = studies; this.viewportData = []; this.layoutTemplateName = 'gridLayout'; this.layoutProps = { rows: 1, columns: 1 }; this.viewportData = []; this.isZoomed = false; } getNumberOfViewports() { return this.layoutProps.rows * this.layoutProps.columns; } setDefaultViewportData() { var numViewports = this.getNumberOfViewports(); var viewportIndex = 0; var self = this; this.studies.forEach(function(study) { study.seriesList.forEach(function(series) { if (!series.instances.length) { return; } var currentViewportData; var existingViewportData = self.viewportData[viewportIndex]; if (self.viewportData[viewportIndex]) { currentViewportData = { viewportIndex: existingViewportData.viewportIndex, studyInstanceUid: existingViewportData.studyInstanceUid, seriesInstanceUid: existingViewportData.seriesInstanceUid, sopInstanceUid: existingViewportData.sopInstanceUid, viewport: existingViewportData.viewport, imageId: existingViewportData.imageId, currentImageIdIndex: existingViewportData.currentImageIdIndex // TODO Remove this once currentImageIdIndex is removed from imageViewerViewports }; self.viewportData[viewportIndex] = currentViewportData; } else { // This tests to make sure there is actually image data in this instance // TODO: Change this when we add PDF and MPEG support // See https://ohiforg.atlassian.net/browse/LT-227 var firstInstance = series.instances[0]; // All imaging modalities must have a valid value for sopClassUid or rows if (!firstInstance || (!isImage(firstInstance.sopClassUid) && !firstInstance.rows)) { currentViewportData = {}; } else { currentViewportData = { viewportIndex: viewportIndex, studyInstanceUid: study.studyInstanceUid, seriesInstanceUid: series.seriesInstanceUid, sopInstanceUid: firstInstance.sopInstanceUid, currentImageIdIndex: 0 // TODO Remove this once currentImageIdIndex is removed from imageViewerViewports }; } self.viewportData.push(currentViewportData); } viewportIndex++; if (viewportIndex === numViewports) { return false; } }); if (viewportIndex === numViewports) { return false; } }); } updateViewports() { log.info('updateViewports'); if (!this.viewportData || !this.viewportData.length || this.viewportData.length !== this.getNumberOfViewports()) { this.setDefaultViewportData(); } // imageViewerViewports occasionally needs relevant layout data in order to set // the element style of the viewport in question var layoutProps = this.layoutProps; var data = $.extend({ viewportData: [] }, layoutProps); this.viewportData.forEach(function(viewportData) { var viewportDataAndLayoutProps = $.extend(viewportData, layoutProps); data.viewportData.push(viewportDataAndLayoutProps); }); var layoutTemplate = Template[this.layoutTemplateName]; $(this.parentNode).html(''); Blaze.renderWithData(layoutTemplate, data, this.parentNode); Session.set('LayoutManagerUpdated', Random.id()); this.isZoomed = false; } /** * This function destroys and re-renders the imageViewerViewport template. * It uses the data provided (object containing seriesIndex and studyIndex) to * load a new series into the produced viewport. * * @param viewportIndex * @param data An object containing a seriesIndex and studyIndex for a study to load into this viewport */ rerenderViewportWithNewSeries(viewportIndex, data) { // The parent container is identified because it is later removed from the DOM var element = $('.imageViewerViewport').eq(viewportIndex); var container = element.parents('.viewportContainer').get(0); // Record the current viewportIndex so this can be passed into the re-rendering call data.viewportIndex = viewportIndex; // Update the dictionary of loaded series for the specified viewport this.viewportData[viewportIndex] = { viewportIndex: viewportIndex, seriesInstanceUid: data.seriesInstanceUid, studyInstanceUid: data.studyInstanceUid, renderedCallback: data.renderedCallback, currentImageIdIndex: 0 }; // Remove the hover styling element.find('canvas').not('.magnifyTool').removeClass('faded'); // Remove the whole template, add in the new one var viewportContainer = element.parents('.removable'); var newViewportContainer = document.createElement('div'); newViewportContainer.className = 'removable'; // Remove the parent element of the template // This is a workaround since otherwise Blaze UI onDestroyed doesn't fire viewportContainer.remove(); container.appendChild(newViewportContainer); // Render and insert the template Blaze.renderWithData(Template.imageViewerViewport, data, newViewportContainer); Session.set('LayoutManagerUpdated', Random.id()); } enlargeViewport(viewportIndex) { log.info('Zooming Into Viewport: ' + viewportIndex); if (!this.viewportData || !this.viewportData.length) { return; } // Clone the array for later this.previousViewportData = this.viewportData.slice(0); var singleViewportData = $.extend({}, this.viewportData[viewportIndex]); singleViewportData.rows = 1; singleViewportData.columns = 1; singleViewportData.viewportIndex = 0; var data = { viewportData: [singleViewportData], rows: 1, columns: 1 }; var layoutTemplate = Template['gridLayout']; $(this.parentNode).html(''); Blaze.renderWithData(layoutTemplate, data, this.parentNode); this.isZoomed = true; this.zoomedViewportIndex = viewportIndex; this.viewportData = data.viewportData; Session.set('LayoutManagerUpdated', Random.id()); } resetPreviousLayout() { if (!this.isZoomed) { return; } this.previousViewportData[this.zoomedViewportIndex] = $.extend({}, this.viewportData[0]); this.previousViewportData[this.zoomedViewportIndex].viewportIndex = this.zoomedViewportIndex; this.viewportData = this.previousViewportData; this.updateViewports(); } toggleEnlargement(viewportIndex) { if (this.isZoomed) { this.resetPreviousLayout(); } else { this.enlargeViewport(viewportIndex); } } };