feat(plugins): Add initial version of ViewportPlugin class (#242)
* Add initial version of ViewportPlugin class * Move entrypoints from OHIFPlugin to OHIF.plugins * Add URL route to DICOMWeb server for Standalone Viewer
This commit is contained in:
1 parent
ced47c765f
commit
53cd12a16a
18 files changed
+708
-562
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { Meteor } from 'meteor/meteor';import { Blaze } from 'meteor/blaze';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Blaze } from 'meteor/blaze';
|
||||
import { Session } from 'meteor/session';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { Template } from 'meteor/templating';
|
||||
@@ -7,6 +8,8 @@ import { $ } from 'meteor/jquery';
|
||||
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
const PLUGIN_CORNERSTONE = 'cornerstone';
|
||||
|
||||
let isInteractingWithViewport = false;
|
||||
Meteor.startup(() => {
|
||||
const setInteracting = flag => {
|
||||
@@ -41,11 +44,16 @@ export class LayoutManager {
|
||||
|
||||
this.isZoomed = false;
|
||||
|
||||
const updateSessionFn = () => Tracker.afterFlush(() => {
|
||||
Session.set('LayoutManagerUpdated', Math.random());
|
||||
const updateSessionFn = () => {
|
||||
const random = Math.random();
|
||||
Session.set('LayoutManagerUpdated', random);
|
||||
this.observer.changed();
|
||||
};
|
||||
|
||||
this.updateSession = _.throttle(updateSessionFn, 300, {
|
||||
leading: true,
|
||||
trailing: false
|
||||
});
|
||||
this.updateSession = _.throttle(updateSessionFn, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +129,8 @@ export class LayoutManager {
|
||||
studyInstanceUid,
|
||||
seriesInstanceUid,
|
||||
displaySetInstanceUid,
|
||||
sopInstanceUid
|
||||
sopInstanceUid,
|
||||
plugin: PLUGIN_CORNERSTONE
|
||||
};
|
||||
|
||||
additionalData.push(data);
|
||||
@@ -181,6 +190,12 @@ export class LayoutManager {
|
||||
this.setDefaultViewportData();
|
||||
}
|
||||
|
||||
this.viewportData.forEach(data => {
|
||||
if (!data.plugin) {
|
||||
data.plugin = PLUGIN_CORNERSTONE;
|
||||
}
|
||||
})
|
||||
|
||||
// imageViewerViewports occasionally needs relevant layout data in order to set
|
||||
// the element style of the viewport in question
|
||||
const layoutProps = this.layoutProps;
|
||||
@@ -190,13 +205,13 @@ export class LayoutManager {
|
||||
|
||||
this.viewportData.forEach(viewportData => {
|
||||
const viewportDataAndLayoutProps = $.extend(viewportData, layoutProps);
|
||||
|
||||
data.viewportData.push(viewportDataAndLayoutProps);
|
||||
});
|
||||
|
||||
const layoutTemplate = Template[this.layoutTemplateName];
|
||||
const $parentNode = $(this.parentNode);
|
||||
|
||||
$parentNode.html('');
|
||||
this.parentNode.innerHTML = '';
|
||||
this.updateLayoutClass();
|
||||
Blaze.renderWithData(layoutTemplate, data, this.parentNode);
|
||||
|
||||
@@ -218,12 +233,15 @@ export class LayoutManager {
|
||||
OHIF.log.info(`LayoutManager rerenderViewportWithNewDisplaySet: ${viewportIndex}`);
|
||||
|
||||
// The parent container is identified because it is later removed from the DOM
|
||||
const element = $('.imageViewerViewport').eq(viewportIndex);
|
||||
const container = element.parents('.viewportContainer').get(0);
|
||||
const container = $('.viewportContainer').get(viewportIndex);
|
||||
|
||||
// Record the current viewportIndex so this can be passed into the re-rendering call
|
||||
data.viewportIndex = viewportIndex;
|
||||
|
||||
// If we have been provided with a plugin to use, use it.
|
||||
// Otherwise, use whichever plugin is currently in use in this viewport.
|
||||
const plugin = data.plugin || this.viewportData[viewportIndex].plugin;
|
||||
|
||||
// Update the dictionary of loaded displaySet for the specified viewport
|
||||
this.viewportData[viewportIndex] = {
|
||||
viewportIndex: viewportIndex,
|
||||
@@ -231,27 +249,40 @@ export class LayoutManager {
|
||||
seriesInstanceUid: data.seriesInstanceUid,
|
||||
studyInstanceUid: data.studyInstanceUid,
|
||||
renderedCallback: data.renderedCallback,
|
||||
currentImageIdIndex: data.currentImageIdIndex || 0
|
||||
currentImageIdIndex: data.currentImageIdIndex || 0,
|
||||
plugin,
|
||||
};
|
||||
|
||||
// Remove the hover styling
|
||||
element.find('canvas').not('.magnifyTool').removeClass('faded');
|
||||
|
||||
// Remove the whole template, add in the new one
|
||||
const viewportContainer = element.parents('.removable');
|
||||
|
||||
const 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);
|
||||
if (plugin === PLUGIN_CORNERSTONE) {
|
||||
// Remove the hover styling
|
||||
const element = $(container).find('.imageViewerViewport');
|
||||
|
||||
element.find('canvas').not('.magnifyTool').removeClass('faded');
|
||||
|
||||
// Remove the whole template, add in the new one
|
||||
const viewportContainer = element.parents('.removable');
|
||||
|
||||
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);
|
||||
|
||||
Blaze.renderWithData(Template.imageViewerViewport, data, newViewportContainer);
|
||||
} else {
|
||||
newViewportContainer.className = `viewport-plugin-${plugin}`;
|
||||
newViewportContainer.style.width = '100%';
|
||||
newViewportContainer.style.height = '100%';
|
||||
|
||||
container.innerHTML = '';
|
||||
container.appendChild(newViewportContainer);
|
||||
}
|
||||
|
||||
this.updateSession();
|
||||
}
|
||||
|
||||
@@ -283,7 +314,7 @@ export class LayoutManager {
|
||||
|
||||
const layoutTemplate = Template.gridLayout;
|
||||
|
||||
$(this.parentNode).html('');
|
||||
this.parentNode.innerHTML = '';
|
||||
Blaze.renderWithData(layoutTemplate, data, this.parentNode);
|
||||
|
||||
this.isZoomed = true;
|
||||
|
||||
+11
-19
@@ -1,5 +1,4 @@
|
||||
// TODO: Add plugin reloader
|
||||
class OHIFPlugin {
|
||||
export class OHIFPlugin {
|
||||
// TODO: this class is still under development and will
|
||||
// likely change in the near future
|
||||
constructor () {
|
||||
@@ -25,10 +24,12 @@ class OHIFPlugin {
|
||||
// reload all the dependency scripts and also
|
||||
// the main plugin script url.
|
||||
static reloadPlugin(plugin) {
|
||||
plugin.scriptURLs = plugin.scriptURLs || {};
|
||||
plugin.scriptURLs.forEach(scriptURL => {
|
||||
this.loadScript(scriptURL).onload = function() {}
|
||||
});
|
||||
console.warn(`reloadPlugin: ${plugin.name}`);
|
||||
if (plugin.scriptURLs && plugin.scriptURLs.length) {
|
||||
plugin.scriptURLs.forEach(scriptURL => {
|
||||
this.loadScript(scriptURL).onload = function() {}
|
||||
});
|
||||
}
|
||||
|
||||
let scriptURL = plugin.url;
|
||||
|
||||
@@ -37,20 +38,11 @@ class OHIFPlugin {
|
||||
}
|
||||
|
||||
this.loadScript(scriptURL).onload = function() {
|
||||
if (OHIFPlugin.entryPoints[plugin.name]) {
|
||||
OHIFPlugin.entryPoints[plugin.name]();
|
||||
const entryPointFunction = OHIF.plugins.entryPoints[plugin.name];
|
||||
|
||||
if (entryPointFunction) {
|
||||
entryPointFunction();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// each plugin registers an entry point function to be called
|
||||
// when the loading is complete (called above in reloadPlugin).
|
||||
|
||||
// TODO: Move to OHIF.plugins.entryPoints?
|
||||
OHIFPlugin.entryPoints = {};
|
||||
|
||||
export { OHIFPlugin };
|
||||
|
||||
// TODO: Should we remove this? Authors should be able to use new 'OHIF.viewerbase.OHIFPlugin()'
|
||||
window.OHIFPlugin = OHIFPlugin;
|
||||
@@ -0,0 +1,169 @@
|
||||
import { Session } from 'meteor/session';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
import { OHIFPlugin } from "./OHIFPlugin";
|
||||
|
||||
export class ViewportPlugin extends OHIFPlugin {
|
||||
constructor(name) {
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
this._destroyed = false;
|
||||
|
||||
this._setupListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a Display Set for a specific viewport by viewport index,
|
||||
* if one is already displayed in the viewport.
|
||||
*
|
||||
* @static
|
||||
* @param {Number} viewportIndex
|
||||
* @return {undefined|ImageSet}
|
||||
*/
|
||||
static getDisplaySet(viewportIndex) {
|
||||
// TODO: Move layoutManager from viewerbase to viewer
|
||||
const { layoutManager } = OHIF.viewerbase;
|
||||
const viewportData = layoutManager.viewportData[viewportIndex];
|
||||
const { studyInstanceUid, displaySetInstanceUid } = viewportData;
|
||||
const studyMetadata = OHIF.viewer.StudyMetadataList.findBy({ studyInstanceUID: studyInstanceUid });
|
||||
|
||||
return studyMetadata.findDisplaySet(displaySet => {
|
||||
return displaySet.displaySetInstanceUid === displaySetInstanceUid;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the viewport using the plugin.
|
||||
*
|
||||
* This should be implemented by the child class.
|
||||
*
|
||||
* @abstract
|
||||
*
|
||||
* @param {HTMLElement} div
|
||||
* @param {ImageSet} displaySet
|
||||
* @param {Object} viewportDetails
|
||||
*/
|
||||
setupViewport(div, displaySet, viewportDetails) {
|
||||
throw new Error('You must override this method!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch a single viewport to use the current ViewportPlugin
|
||||
*
|
||||
* @param {Number} viewportIndex The viewport to switch to the current plugin type
|
||||
*/
|
||||
setViewportToPlugin(viewportIndex) {
|
||||
if (!this.name) {
|
||||
throw new Error('ViewportPlugin subclasses must have a name');
|
||||
}
|
||||
|
||||
const { layoutManager } = OHIF.viewerbase;
|
||||
const viewportData = layoutManager.viewportData[viewportIndex];
|
||||
if (viewportData.plugin === this.name) {
|
||||
OHIF.log.info(`setViewportToPlugin: Viewport ${viewportIndex} already set to plugin ${this.name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
viewportData.plugin = this.name;
|
||||
|
||||
layoutManager.rerenderViewportWithNewDisplaySet(viewportIndex, viewportData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs 'setupViewport' for the ViewportPlugin on all viewports which should
|
||||
* be rendered by this plugin, but have not yet been initialized. Viewports
|
||||
* which already contain contents are skipped.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_initEmptyPluginViewports() {
|
||||
if (!this.name) {
|
||||
throw new Error('ViewportPlugin subclasses must have a name');
|
||||
}
|
||||
|
||||
// Find all Viewport HTMLElements currently using this plugin
|
||||
const pluginDivs = Array.from(document.querySelectorAll(`.viewport-plugin-${this.name}`));
|
||||
|
||||
// If there are no Viewports using this plugin, stop here
|
||||
if (!pluginDivs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emptyPluginDivs = pluginDivs.filter(div => {
|
||||
// Keep only divs owned by the plugin which have no contents
|
||||
return div.innerHTML.trim() === '';
|
||||
});
|
||||
|
||||
OHIF.log.info(`${this.name}: Initializing ${emptyPluginDivs.length} viewports`);
|
||||
|
||||
// Retrieve the list of all viewports, so we can figure out the viewport details
|
||||
const allViewports = Array.from(document.querySelectorAll('.viewportContainer'));
|
||||
|
||||
emptyPluginDivs.forEach(div => {
|
||||
// Identify the Viewport index, and any display set that is currently
|
||||
// hung in the viewport
|
||||
const viewportIndex = allViewports.indexOf(div.parentNode);
|
||||
const viewportDetails = { viewportIndex };
|
||||
const displaySet = ViewportPlugin.getDisplaySet(viewportIndex);
|
||||
|
||||
// Use the plugin's setupViewport function to render the contents
|
||||
// of this viewport.
|
||||
this.setupViewport(div, viewportDetails, displaySet);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for changes to the viewport layout which would necessitate a
|
||||
* rerendering of the viewports. When this happens, re-render all viewports
|
||||
* which are using this plugin.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_setupListeners() {
|
||||
if (!this.name) {
|
||||
throw new Error('ViewportPlugin subclasses must have a name');
|
||||
}
|
||||
|
||||
console.warn(`_setupListeners: ${this.name}`);
|
||||
|
||||
// TODO: Stop using Meteor's reactivity here
|
||||
Tracker.autorun((computation) => {
|
||||
const random = Session.get('LayoutManagerUpdated');
|
||||
console.warn(`LayoutManagerUpdated: ${this.name}: ${random}`);
|
||||
|
||||
// Bail out if this is the first time the autorun
|
||||
// executes (i.e. when it is being defined).
|
||||
//
|
||||
// Note: This has to be checked after the dependency on the
|
||||
// Session variable above, or the reactive dependency will not
|
||||
// be established.
|
||||
if (computation.firstRun === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In case we need to disable the use
|
||||
// of this plugin, we can also stop the
|
||||
// reactive computation by setting
|
||||
// this.destroyed to true.
|
||||
if (this._destroyed === true) {
|
||||
computation.stop();
|
||||
}
|
||||
|
||||
// Identify all viewports which should be
|
||||
// rendered by the ViewportPlugin, and render
|
||||
// them.
|
||||
this._initEmptyPluginViewports();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening for changes to the viewport layout in order to
|
||||
* automatically rerender viewports setup for use by this plugin.
|
||||
*/
|
||||
stopListeners() {
|
||||
this._destroyed = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
import { OHIFPlugin } from './OHIFPlugin';
|
||||
import { ViewportPlugin } from './ViewportPlugin';
|
||||
|
||||
// Each plugin registers an entry point function to be called
|
||||
// when the loading is complete.
|
||||
|
||||
const plugins = {
|
||||
OHIFPlugin,
|
||||
ViewportPlugin,
|
||||
entryPoints: {}
|
||||
};
|
||||
|
||||
// TODO: When we reorganize the packages, we should figure out where to put this.
|
||||
OHIF.plugins = plugins;
|
||||
|
||||
export default plugins;
|
||||
Reference in new issue
Block a user