💥 _explosion noises_
This commit is contained in:
parent
ca98e69a66
commit
7e68259edb
12
.gitmodules
vendored
12
.gitmodules
vendored
@ -1,12 +0,0 @@
|
||||
[submodule "Packages-react/ohif-core"]
|
||||
path = Packages-react/ohif-core
|
||||
url = https://github.com/OHIF/ohif-core.git
|
||||
[submodule "Packages-react/react-viewerbase"]
|
||||
path = Packages-react/react-viewerbase
|
||||
url = https://github.com/OHIF/react-viewerbase.git
|
||||
[submodule "Packages-react/react-cornerstone-viewport"]
|
||||
path = Packages-react/react-cornerstone-viewport
|
||||
url = https://github.com/cornerstonejs/react-cornerstone-viewport.git
|
||||
[submodule "Packages-react/react-vtkjs-viewport"]
|
||||
path = Packages-react/react-vtkjs-viewport
|
||||
url = https://github.com/OHIF/react-vtkjs-viewport.git
|
||||
6
OHIFViewer/.gitignore
vendored
6
OHIFViewer/.gitignore
vendored
@ -1,6 +0,0 @@
|
||||
.idea
|
||||
.meteor/local
|
||||
.meteor/meteorite
|
||||
node_modules
|
||||
client/sha.js
|
||||
client/version.js
|
||||
@ -1,19 +0,0 @@
|
||||
# This file contains information which helps Meteor properly upgrade your
|
||||
# app when you run 'meteor update'. You should check it into version control
|
||||
# with your project.
|
||||
|
||||
notices-for-0.9.0
|
||||
notices-for-0.9.1
|
||||
0.9.4-platform-file
|
||||
notices-for-facebook-graph-api-2
|
||||
1.2.0-standard-minifiers-package
|
||||
1.2.0-meteor-platform-split
|
||||
1.2.0-cordova-changes
|
||||
1.2.0-breaking-changes
|
||||
1.3.0-split-minifiers-package
|
||||
1.3.5-remove-old-dev-bundle-link
|
||||
1.4.0-remove-old-dev-bundle-link
|
||||
1.4.1-add-shell-server-package
|
||||
1.4.3-split-account-service-packages
|
||||
1.5-add-dynamic-import-package
|
||||
1.7-split-underscore-from-meteor-base
|
||||
2
OHIFViewer/.meteor/.gitignore
vendored
2
OHIFViewer/.meteor/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
dev_bundle
|
||||
local
|
||||
@ -1,7 +0,0 @@
|
||||
# This file contains a token that is unique to your project.
|
||||
# Check it into your repository along with the rest of this directory.
|
||||
# It can be used for purposes such as:
|
||||
# - ensuring you don't accidentally deploy one app on top of another
|
||||
# - providing package authors with aggregated statistics
|
||||
|
||||
1jfkhvr1ljfqodmpn32m
|
||||
@ -1,38 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
# Check this file (and the other files in this directory) into your repository.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
http@1.4.2
|
||||
promise@0.11.1
|
||||
stylus@2.513.13
|
||||
meteor-base@1.4.0
|
||||
mobile-experience@1.0.5
|
||||
mongo@1.6.0
|
||||
blaze-html-templates@1.0.4
|
||||
session@1.2.0-beta181.7
|
||||
tracker@1.2.0
|
||||
logging@1.1.20
|
||||
reload@1.2.0
|
||||
ejson@1.1.0
|
||||
spacebars@1.0.12
|
||||
check@1.3.1
|
||||
ecmascript@0.12.3
|
||||
reactive-var@1.0.11
|
||||
reactive-dict@1.3.0-beta181.7
|
||||
standard-minifier-css@1.5.2
|
||||
standard-minifier-js@2.4.0
|
||||
shell-server@0.4.0
|
||||
|
||||
# OHIF Packages
|
||||
ohif:cornerstone
|
||||
ohif:viewerbase
|
||||
ohif:study-list
|
||||
ohif:hanging-protocols
|
||||
|
||||
fortawesome:fontawesome
|
||||
aldeed:simple-schema # Third party package to deal with schemas
|
||||
aldeed:template-extension
|
||||
react-template-helper
|
||||
gadicc:blaze-react-component
|
||||
@ -1,2 +0,0 @@
|
||||
server
|
||||
browser
|
||||
@ -1 +0,0 @@
|
||||
METEOR@1.8.1-beta.7
|
||||
@ -1,100 +0,0 @@
|
||||
aldeed:collection2@2.10.0
|
||||
aldeed:collection2-core@1.2.0
|
||||
aldeed:schema-deny@1.1.0
|
||||
aldeed:schema-index@1.1.1
|
||||
aldeed:simple-schema@1.5.4
|
||||
aldeed:template-extension@4.1.0
|
||||
allow-deny@1.1.0
|
||||
autoupdate@1.6.0-beta181.7
|
||||
babel-compiler@7.2.4
|
||||
babel-runtime@1.3.0
|
||||
base64@1.0.11
|
||||
binary-heap@1.0.11
|
||||
blaze@2.3.3
|
||||
blaze-html-templates@1.1.2
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.6.0
|
||||
caching-compiler@1.2.1
|
||||
caching-html-compiler@1.1.3
|
||||
callback-hook@1.1.0
|
||||
check@1.3.1
|
||||
ddp@1.4.0
|
||||
ddp-client@2.3.3
|
||||
ddp-common@1.4.0
|
||||
ddp-server@2.3.0-beta181.7
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.1
|
||||
dynamic-import@0.5.1
|
||||
ecmascript@0.12.4
|
||||
ecmascript-runtime@0.7.0
|
||||
ecmascript-runtime-client@0.8.0
|
||||
ecmascript-runtime-server@0.7.1
|
||||
ejson@1.1.0
|
||||
es5-shim@4.8.0
|
||||
fetch@0.1.0
|
||||
fortawesome:fontawesome@4.7.0
|
||||
gadicc:blaze-react-component@1.4.1
|
||||
geojson-utils@1.0.10
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
http@1.4.2
|
||||
id-map@1.1.0
|
||||
inter-process-messaging@0.1.0
|
||||
jquery@1.11.11
|
||||
launch-screen@1.1.1
|
||||
livedata@1.0.18
|
||||
logging@1.1.20
|
||||
mdg:validation-error@0.5.1
|
||||
meteor@1.9.2
|
||||
meteor-base@1.4.0
|
||||
minifier-css@1.4.1
|
||||
minifier-js@2.4.0
|
||||
minimongo@1.4.5
|
||||
mobile-experience@1.0.5
|
||||
mobile-status-bar@1.0.14
|
||||
modern-browsers@0.1.4-beta181.7
|
||||
modules@0.13.0
|
||||
modules-runtime@0.10.3
|
||||
mongo@1.6.0
|
||||
mongo-decimal@0.1.0
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.7
|
||||
natestrauser:select2@4.0.3
|
||||
npm-mongo@3.1.2-beta181.7
|
||||
observe-sequence@1.0.16
|
||||
ohif:cornerstone@0.0.1
|
||||
ohif:hanging-protocols@0.0.1
|
||||
ohif:study-list@0.0.1
|
||||
ohif:themes@0.0.1
|
||||
ohif:themes-common@0.0.1
|
||||
ohif:viewerbase@0.0.1
|
||||
ordered-dict@1.1.0
|
||||
promise@0.11.2
|
||||
raix:eventemitter@0.1.3
|
||||
random@1.1.0
|
||||
react-template-helper@0.2.11
|
||||
reactive-dict@1.3.0-beta181.7
|
||||
reactive-var@1.0.11
|
||||
reload@1.2.0
|
||||
retry@1.1.0
|
||||
routepolicy@1.1.0
|
||||
session@1.2.0
|
||||
shell-server@0.4.0
|
||||
socket-stream-client@0.2.2
|
||||
spacebars@1.0.15
|
||||
spacebars-compiler@1.1.3
|
||||
standard-minifier-css@1.5.2
|
||||
standard-minifier-js@2.4.0
|
||||
stylus@2.513.14
|
||||
templating@1.3.2
|
||||
templating-compiler@1.3.3
|
||||
templating-runtime@1.3.2
|
||||
templating-tools@1.1.2
|
||||
tmeasday:check-npm-versions@0.3.2
|
||||
tracker@1.2.0
|
||||
ui@1.0.13
|
||||
underscore@1.0.10
|
||||
url@1.2.0
|
||||
webapp@1.7.2
|
||||
webapp-hashing@1.0.9
|
||||
@ -1,2 +0,0 @@
|
||||
set METEOR_PACKAGE_DIRS=..\Packages
|
||||
meteor --settings ../config/medkenOrthanc.json
|
||||
@ -1 +0,0 @@
|
||||
METEOR_PACKAGE_DIRS="../Packages" meteor --settings ../config/medkenOrthanc.json
|
||||
@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
declare config='../config/orthancDICOMWeb.json'
|
||||
|
||||
if [ $# -gt 0 ]; then
|
||||
if [ "$1" = '--dimse' ]; then
|
||||
config="${config%/*}/orthancDIMSE.json"
|
||||
[ -f "$config" ] && echo "DIMSE config file selected: $config"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo 'Starting Meteor server...'
|
||||
METEOR_PACKAGE_DIRS="../Packages" meteor --settings "$config"
|
||||
@ -1,2 +0,0 @@
|
||||
set METEOR_PACKAGE_DIRS=..\Packages
|
||||
meteor --settings ../config/orthancDICOMWeb.json
|
||||
@ -1,2 +0,0 @@
|
||||
echo "Starting Meteor server..."
|
||||
METEOR_PACKAGE_DIRS="../Packages" meteor --settings ../config/orthancDICOMWeb.json
|
||||
@ -1,2 +0,0 @@
|
||||
echo "Starting Meteor server..."
|
||||
METEOR_PACKAGE_DIRS="../Packages" meteor --settings ../config/orthancDIMSE.json
|
||||
@ -1,2 +0,0 @@
|
||||
echo "Starting Meteor server..."
|
||||
METEOR_PACKAGE_DIRS="../Packages" meteor --settings ../config/publicOrthancDICOMWeb.json
|
||||
@ -1,40 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
##################################################
|
||||
# Runs the packages tests
|
||||
##################################################
|
||||
|
||||
# check execution arguments
|
||||
while [ "$1" != "" ]; do
|
||||
PARAM=`echo $1 | awk -F= '{print $1}'`
|
||||
case $PARAM in
|
||||
-c | --coverage)
|
||||
export RUN_COVERAGE=1
|
||||
;;
|
||||
-v | --verbose)
|
||||
export COVERAGE_VERBOSE=1
|
||||
;;
|
||||
-s | --spacejam)
|
||||
export RUN_SPACEJAM=1
|
||||
;;
|
||||
*)
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "$RUN_COVERAGE" == 1 ];
|
||||
then
|
||||
# Setting coverage variables
|
||||
app_folder=$(pwd)
|
||||
app_folder+="/packages/ohif-viewerbase/"
|
||||
export COVERAGE_APP_FOLDER=$app_folder
|
||||
export COVERAGE=1
|
||||
echo 'Running meteor-coverage'
|
||||
fi
|
||||
|
||||
if [ "$RUN_SPACEJAM" == 1 ];
|
||||
then
|
||||
spacejam-mocha ./packages/ohif-viewerbase/
|
||||
else
|
||||
meteor test-packages --driver-package='cultofcoders:mocha' ./packages/ohif-viewerbase/
|
||||
fi
|
||||
@ -1,16 +0,0 @@
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
background-color: black;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { withRouter } from 'react-router';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import ViewerRouting from "./ViewerRouting.js";
|
||||
import IHEInvokeImageDisplay from './IHEInvokeImageDisplay.js';
|
||||
import './App.css';
|
||||
|
||||
const reload = () => window.location.reload();
|
||||
const Studylist = OHIF.studylist.components.StudyList;
|
||||
|
||||
function setContext(context) {
|
||||
/*Rollbar.configure({
|
||||
payload: {
|
||||
context
|
||||
}
|
||||
});*/
|
||||
console.log(context);
|
||||
}
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
// Temporary until the rest of the UI is in React
|
||||
window.router = {
|
||||
history: this.props.history
|
||||
};
|
||||
|
||||
this.unlisten = this.props.history.listen((location, action) => {
|
||||
setContext(window.location.pathname);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unlisten();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/studylist"
|
||||
component={Studylist}
|
||||
/*auth={this.props.auth}*/
|
||||
store={this.props.store}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/"
|
||||
component={Studylist}
|
||||
/*auth={this.props.auth}*/
|
||||
store={this.props.store}
|
||||
/>
|
||||
<Route
|
||||
path="/viewer/:studyInstanceUids"
|
||||
component={ViewerRouting}
|
||||
/*auth={this.props.auth}*/
|
||||
store={this.props.store}
|
||||
/>
|
||||
<Route
|
||||
path="/study/:studyInstanceUid/series/:seriesInstanceUids"
|
||||
component={ViewerRouting}
|
||||
/*auth={this.props.auth}*/
|
||||
store={this.props.store}
|
||||
/>
|
||||
<Route
|
||||
path="/IHEInvokeImageDisplay"
|
||||
component={IHEInvokeImageDisplay}
|
||||
/*auth={this.props.auth}*/
|
||||
store={this.props.store}
|
||||
/>
|
||||
|
||||
{/*<Route path="/silent-refresh.html" onEnter={reload} />
|
||||
<Route path="/logout-redirect.html" onEnter={reload} />*/}
|
||||
<Route render={() =>
|
||||
<div> Sorry, this page does not exist. </div>}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(App);
|
||||
@ -1,32 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Viewer from "./viewer/viewer.js";
|
||||
|
||||
function IHEInvokeImageDisplay({ match }) {
|
||||
const requestType = match.params.query.requestType;
|
||||
|
||||
let studyInstanceUids;
|
||||
let displayStudyList = false;
|
||||
if (requestType === "STUDY") {
|
||||
studyInstanceUids = match.params.query.studyUID.split(';');
|
||||
} else if (requestType === "STUDYBASE64") {
|
||||
const uids = this.params.query.studyUID;
|
||||
const decodedData = window.atob(uids);
|
||||
studyInstanceUids = decodedData.split(';');
|
||||
} else if (requestType === "PATIENT") {
|
||||
const patientUids = this.params.query.patientID.split(';');
|
||||
displayStudyList = true
|
||||
} else {
|
||||
displayStudyList = true
|
||||
}
|
||||
|
||||
if (displayStudyList) {
|
||||
return (<StudyList/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewerFromStudyData studyInstanceUids={studyInstanceUids}/>
|
||||
);
|
||||
}
|
||||
|
||||
export default IHEInvokeImageDisplay;
|
||||
@ -1,99 +0,0 @@
|
||||
import React, {Component} from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Viewer from "./viewer/viewer.js";
|
||||
import { OHIF } from 'ohif-core';
|
||||
|
||||
function createDisplaySets(studies) {
|
||||
// Define the OHIF.viewer.data global object
|
||||
OHIF.viewer.data = OHIF.viewer.data || {};
|
||||
|
||||
// @TypeSafeStudies
|
||||
// Clears OHIF.viewer.Studies collection
|
||||
OHIF.viewer.Studies.removeAll();
|
||||
|
||||
// @TypeSafeStudies
|
||||
// Clears OHIF.viewer.StudyMetadataList collection
|
||||
OHIF.viewer.StudyMetadataList.removeAll();
|
||||
|
||||
OHIF.viewer.data.studyInstanceUids = [];
|
||||
|
||||
const updatedStudies = studies.map(study => {
|
||||
const studyMetadata = new OHIF.metadata.OHIFStudyMetadata(study, study.studyInstanceUid);
|
||||
let displaySets = study.displaySets;
|
||||
|
||||
if (!study.displaySets) {
|
||||
displaySets = OHIF.viewerbase.sortingManager.getDisplaySets(studyMetadata);
|
||||
study.displaySets = displaySets;
|
||||
}
|
||||
|
||||
studyMetadata.setDisplaySets(displaySets);
|
||||
|
||||
study.selected = true;
|
||||
OHIF.viewer.Studies.insert(study);
|
||||
OHIF.viewer.StudyMetadataList.insert(studyMetadata);
|
||||
OHIF.viewer.data.studyInstanceUids.push(study.studyInstanceUid);
|
||||
|
||||
// Updates WADO-RS metaDataManager
|
||||
OHIF.viewerbase.updateMetaDataManager(study);
|
||||
|
||||
return study;
|
||||
});
|
||||
|
||||
return updatedStudies;
|
||||
}
|
||||
|
||||
class ViewerFromStudyData extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
studies: null,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// TODO: Avoid using timepoints here
|
||||
//const params = { studyInstanceUids, seriesInstanceUids, timepointId, timepointsFilter={} };
|
||||
const params = {
|
||||
studyInstanceUids: this.props.studyInstanceUids,
|
||||
seriesInstanceUids: this.props.seriesInstanceUids,
|
||||
}
|
||||
const promise = OHIF.viewerbase.prepareViewerData(params);
|
||||
|
||||
// Render the viewer when the data is ready
|
||||
promise.then(({ studies, viewerData }) => {
|
||||
OHIF.viewer.data = viewerData;
|
||||
|
||||
const updatedStudies = createDisplaySets(studies);
|
||||
|
||||
this.setState({
|
||||
studies: updatedStudies,
|
||||
});
|
||||
}).catch(error => {
|
||||
this.setState({
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return (<div>Error: {JSON.stringify(this.state.error)}</div>);
|
||||
} else if (!this.state.studies) {
|
||||
return (<div>Loading...</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<Viewer studies={this.state.studies}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ViewerFromStudyData.propTypes = {
|
||||
studyInstanceUids: PropTypes.array.isRequired,
|
||||
seriesInstanceUids: PropTypes.array
|
||||
};
|
||||
|
||||
export default ViewerFromStudyData;
|
||||
@ -1,32 +0,0 @@
|
||||
import React, {Component} from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ViewerFromStudyData from "./ViewerFromStudyData.js";
|
||||
|
||||
function ViewerRouting({ match }) {
|
||||
const { studyInstanceUids, seriesInstanceUids } = match.params;
|
||||
|
||||
let studyUIDs;
|
||||
let seriesUIDs;
|
||||
|
||||
if (studyInstanceUids && !seriesInstanceUids) {
|
||||
studyUIDs = studyInstanceUids.split(';');
|
||||
} else if (studyInstanceUids && seriesInstanceUids) {
|
||||
studyUIDs = [match.params.studyInstanceUid];
|
||||
seriesUIDs = match.params.seriesInstanceUids.split(';');
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewerFromStudyData studyInstanceUids={studyUIDs} seriesInstanceUids={seriesUIDs}/>
|
||||
);
|
||||
}
|
||||
|
||||
ViewerRouting.propTypes = {
|
||||
match: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
studyInstanceUids: PropTypes.string.isRequired,
|
||||
seriesInstanceUids: PropTypes.string
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
export default ViewerRouting;
|
||||
@ -1,171 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
import { StudyBrowser } from 'react-viewerbase';
|
||||
|
||||
/**
|
||||
* Asynchronous wrapper around Cornerstone's renderToCanvas method.
|
||||
*
|
||||
* @param {HTMLElement} canvasElement An HTML <canvas> element
|
||||
* @param {Image} image A Cornerstone Image
|
||||
*
|
||||
* @return {Promise} A promise tracking the progress of the rendering. Resolves empty.
|
||||
*/
|
||||
function renderAsync(canvasElement, image) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
cornerstone.renderToCanvas(canvasElement, image);
|
||||
resolve();
|
||||
} catch(error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Define a handler for success on image load
|
||||
const loadSuccess = function(image, studyInstanceUid, displaySetInstanceUid) {
|
||||
// This is an off-screen canvas. It's used to get dataURL images by using
|
||||
// cornerstone.renderToCanvas function.
|
||||
const canvasElement = document.createElement('canvas');
|
||||
canvasElement.width = 193;
|
||||
canvasElement.height = 123;
|
||||
|
||||
// Render the image to canvas to be able to get its dataURL
|
||||
renderAsync(canvasElement, image).then(() => {
|
||||
const studies = this.state.studiesForBrowser;
|
||||
const study = studies.find(study => study.studyInstanceUid === studyInstanceUid);
|
||||
const thumbnail = study.thumbnails.find(t => t.displaySetInstanceUid === displaySetInstanceUid);
|
||||
|
||||
thumbnail.imageSrc = canvasElement.toDataURL('image/jpeg', 1);
|
||||
|
||||
this.setState({
|
||||
studiesForBrowser: studies
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Define a handler for error on image load
|
||||
const loadError = function(error, studyInstanceUid, displaySetInstanceUid) {
|
||||
const studies = this.state.studiesForBrowser;
|
||||
const study = studies.find(study => study.studyInstanceUid === studyInstanceUid);
|
||||
const thumbnail = study.thumbnails.find(t => t.displaySetInstanceUid === displaySetInstanceUid);
|
||||
|
||||
thumbnail.error = error;
|
||||
|
||||
this.setState({
|
||||
studiesForBrowser: studies
|
||||
});
|
||||
};
|
||||
|
||||
const ViewerMain = Viewerbase.components.viewer.ViewerMain;
|
||||
|
||||
class FlexboxLayout extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
leftSidebarOpen: true,
|
||||
rightSidebarOpen: false,
|
||||
studiesForBrowser: this.getStudiesForBrowser(),
|
||||
};
|
||||
|
||||
this.getStudiesForBrowser = this.getStudiesForBrowser.bind(this);
|
||||
this.getThumbnailsFromImageIds = this.getThumbnailsFromImageIds.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getThumbnailsFromImageIds();
|
||||
}
|
||||
|
||||
getThumbnailsFromImageIds() {
|
||||
const studyThumbnails = this.state.studiesForBrowser.forEach(function (study) {
|
||||
const { studyInstanceUid } = study;
|
||||
|
||||
study.thumbnails.forEach(function (displaySet) {
|
||||
const imageId = displaySet.imageId;
|
||||
const {displaySetInstanceUid, seriesDescription, seriesNumber, instanceNumber, numImageFrames} = displaySet;
|
||||
|
||||
cornerstone.loadAndCacheImage(imageId).then((image) => {
|
||||
loadSuccess.call(this, image, studyInstanceUid, displaySetInstanceUid);
|
||||
}, error => {
|
||||
loadError.call(this, error, studyInstanceUid, displaySetInstanceUid);
|
||||
});
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
}
|
||||
|
||||
getStudiesForBrowser() {
|
||||
// @TypeSafeStudies
|
||||
const studies = OHIF.viewer.Studies.findAllBy({
|
||||
selected: true
|
||||
});
|
||||
|
||||
|
||||
// TODO[react]:
|
||||
// - Add sorting of display sets
|
||||
// - Pass in which display set is active from Redux
|
||||
// - Pass in errors and stack loading progress from Redux
|
||||
// - Add useMiddleSeriesInstanceAsThumbnail
|
||||
// - Add showStackLoadingProgressBar option
|
||||
return studies.map((study) => {
|
||||
const { studyInstanceUid } = study;
|
||||
|
||||
const thumbnails = study.displaySets.map((displaySet) => {
|
||||
const active = false;
|
||||
const stackPercentComplete = 0;
|
||||
const { displaySetInstanceUid, seriesDescription, seriesNumber, instanceNumber, numImageFrames } = displaySet;
|
||||
const imageId = displaySet.images[0].getImageId();
|
||||
return {
|
||||
imageSrc: '',
|
||||
imageId,
|
||||
displaySetInstanceUid,
|
||||
seriesDescription,
|
||||
seriesNumber,
|
||||
instanceNumber,
|
||||
numImageFrames,
|
||||
active,
|
||||
stackPercentComplete
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
studyInstanceUid,
|
||||
thumbnails
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
let mainContentClassName = "mainContent"
|
||||
if (this.state.leftSidebarOpen) {
|
||||
mainContentClassName += ' sidebar-left-open';
|
||||
}
|
||||
|
||||
if (this.state.rightSidebarOpen) {
|
||||
mainContentClassName += ' sidebar-right-open';
|
||||
}
|
||||
|
||||
// TODO[react]: Add measurementLightTable
|
||||
return (
|
||||
<div className="viewerSection">
|
||||
<div className={this.state.leftSidebarOpen ? "sidebarMenu sidebar-left sidebar-open" : "sidebarMenu sidebar-left"}>
|
||||
<StudyBrowser studies={this.state.studiesForBrowser}/>
|
||||
</div>
|
||||
<div className={mainContentClassName}>
|
||||
<ViewerMain studies={this.props.studies}/>
|
||||
</div>
|
||||
<div className={this.state.rightSidebarOpen ? "sidebarMenu sidebar-right sidebar-open" : "sidebarMenu sidebar-right"}>
|
||||
{/*{{> measurementLightTable (clone this)}}*/}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FlexboxLayout.propTypes = {
|
||||
studies: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default FlexboxLayout;
|
||||
@ -1,67 +0,0 @@
|
||||
@require '{ohif:viewerbase}/app'
|
||||
|
||||
.viewerSection
|
||||
display: flex
|
||||
flex: 1
|
||||
flex-flow: row nowrap
|
||||
align-items: stretch
|
||||
height: 'calc(100% - %s)' % ($toolbarHeight)
|
||||
width: 100%
|
||||
|
||||
.sidebarMenu
|
||||
height: 100%
|
||||
// required transformation to make inner fixed elements relative to this one
|
||||
transform(scale(1))
|
||||
transition($sidebarTransition)
|
||||
|
||||
.sidebar-option
|
||||
height: 100%
|
||||
max-width: inherit
|
||||
position: absolute
|
||||
transform(translateX(100%))
|
||||
transition($sidebarTransition)
|
||||
width: 100%
|
||||
|
||||
&.active
|
||||
transform(translateX(0%))
|
||||
|
||||
.sidebar-left
|
||||
theme('border-right', '%s solid $uiBorderColor' % $uiBorderThickness)
|
||||
flex: 1
|
||||
margin-left: - $studiesSidebarMenuWidth
|
||||
max-width: $studiesSidebarMenuWidth
|
||||
order: 1
|
||||
|
||||
&.sidebar-open
|
||||
margin-left: 0
|
||||
|
||||
.mainContent
|
||||
flex: 1
|
||||
height: 100%
|
||||
order: 2
|
||||
overflow: hidden
|
||||
transition($sidebarTransition)
|
||||
width: 100%
|
||||
|
||||
.sidebar-right
|
||||
flex: 1
|
||||
margin-right: - $rightSidebarMenuWidth
|
||||
max-width: $rightSidebarMenuWidth
|
||||
order: 3
|
||||
position: relative
|
||||
|
||||
&[data-timepoints="3"]
|
||||
margin-right: - ($rightSidebarMenuWidth + 135.5px)
|
||||
max-width: $rightSidebarMenuWidth + 135.5px
|
||||
|
||||
&[data-timepoints="4"]
|
||||
margin-right: - ($rightSidebarMenuWidth + 270px)
|
||||
max-width: $rightSidebarMenuWidth + 270px
|
||||
|
||||
&.sidebar-open
|
||||
margin-right: 0
|
||||
|
||||
.studiesListedChanger
|
||||
theme('border-bottom', '%s solid $uiBorderColor' % $uiBorderThickness)
|
||||
padding: 20px 10px
|
||||
text-align: center
|
||||
@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore } from 'redux';
|
||||
import App from './App.js';
|
||||
import { OHIF } from 'ohif-core';
|
||||
|
||||
const store = createStore(OHIF.redux.combinedReducer);
|
||||
|
||||
// TODO[react] Use a provider when the whole tree is React
|
||||
window.store = store;
|
||||
|
||||
Meteor.startup(() => {
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<App store={store}/>
|
||||
</BrowserRouter>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
});
|
||||
@ -1,34 +0,0 @@
|
||||
<template name="ohifViewer">
|
||||
{{#header brandHref='http://ohif.org' headerClasses=instance.headerClasses}}
|
||||
{{#section 'brand'}}
|
||||
<svg class="logo-image">
|
||||
<use xlink:href={{absoluteUrl "packages/ohif_viewerbase/assets/icons.svg#icon-ohif-logo"}}></use>
|
||||
</svg>
|
||||
<div class="logo-text">Open Health Imaging Foundation</div>
|
||||
{{/section}}
|
||||
{{#section 'headerAfterBrand'}}
|
||||
{{#if studyListToggleText}}
|
||||
<a href="#" class="btn studyListLinkSection pull-left js-toggle-studyList">
|
||||
{{studyListToggleText}}
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/section}}
|
||||
{{#section 'headerMenu'}}
|
||||
<span class="research-use pull-left m-r-1">
|
||||
INVESTIGATIONAL USE ONLY
|
||||
</span>
|
||||
<span class="header-options pull-left m-r-1">Options</span>
|
||||
<div class="menu-toggle pull-right">
|
||||
<span class="caret-down"></span>
|
||||
</div>
|
||||
{{/section}}
|
||||
{{/header}}
|
||||
|
||||
{{ #if eq this.template 'studylist' }}
|
||||
{{>Studylist data=this}}
|
||||
{{ else }}
|
||||
<div class="test">
|
||||
{{> React component=Viewer studies=studies}}
|
||||
</div>
|
||||
{{ /if }}
|
||||
</template>
|
||||
@ -1,88 +0,0 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import { Session } from 'meteor/session';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { OHIF } from 'ohif-core';
|
||||
|
||||
import Viewer from '../viewer/viewer.js';
|
||||
|
||||
Template.ohifViewer.onCreated(() => {
|
||||
const instance = Template.instance();
|
||||
instance.headerClasses = new ReactiveVar('');
|
||||
|
||||
const headerItems = [{
|
||||
action: () => OHIF.ui.showDialog('serverInformationModal'),
|
||||
text: 'Server Information',
|
||||
icon: 'fa fa-server fa-lg',
|
||||
separatorAfter: true
|
||||
}, {
|
||||
action: () => OHIF.ui.showDialog('themeSelectorModal'),
|
||||
text: 'Themes',
|
||||
iconClasses: 'theme',
|
||||
iconSvgUse: 'packages/ohif_viewerbase/assets/icons.svg#theme',
|
||||
separatorAfter: false
|
||||
}, {
|
||||
action: () => OHIF.ui.showDialog('userPreferencesDialog'),
|
||||
text: 'Preferences',
|
||||
icon: 'fa fa-user',
|
||||
separatorAfter: true
|
||||
}, {
|
||||
action: () => OHIF.ui.showDialog('aboutModal'),
|
||||
text: 'About',
|
||||
icon: 'fa fa-info'
|
||||
}];
|
||||
|
||||
if (OHIF.user.userLoggedIn() === true) {
|
||||
headerItems.push({
|
||||
action: OHIF.user.logout,
|
||||
text: 'Logout',
|
||||
iconClasses: 'logout',
|
||||
iconSvgUse: 'packages/ohif_viewerbase/assets/user-menu-icons.svg#logout'
|
||||
});
|
||||
}
|
||||
|
||||
OHIF.header.dropdown.setItems(headerItems);
|
||||
|
||||
/*instance.autorun(() => {
|
||||
const currentRoute = Router.current();
|
||||
if (!currentRoute) return;
|
||||
const routeName = currentRoute.route.getName();
|
||||
const isViewer = routeName.indexOf('viewer') === 0;
|
||||
|
||||
// Add or remove the strech class from body
|
||||
$(document.body)[isViewer ? 'addClass' : 'removeClass']('stretch');
|
||||
|
||||
// Set the header on its bigger version if the viewer is not opened
|
||||
instance.headerClasses.set(isViewer ? '' : 'header-big');
|
||||
|
||||
// Set the viewer open state on session
|
||||
Session.set('ViewerOpened', isViewer);
|
||||
});*/
|
||||
});
|
||||
|
||||
Template.ohifViewer.events({
|
||||
'click .js-toggle-studyList'(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
Template.ohifViewer.helpers({
|
||||
studyListToggleText() {
|
||||
const instance = Template.instance();
|
||||
const isViewer = Session.get('ViewerOpened');
|
||||
|
||||
if (isViewer) {
|
||||
instance.hasViewerData = true;
|
||||
return 'Study list';
|
||||
}
|
||||
|
||||
return instance.hasViewerData ? 'Back to viewer' : '';
|
||||
},
|
||||
|
||||
Viewer() {
|
||||
return Viewer;
|
||||
},
|
||||
|
||||
studies() {
|
||||
return Template.instance().data.studies;
|
||||
}
|
||||
});
|
||||
@ -1,80 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
body>.header
|
||||
|
||||
.brand
|
||||
height: 30px
|
||||
display: inline-block
|
||||
text-decoration: none
|
||||
|
||||
.logo-image
|
||||
display: inline-block
|
||||
fill: transparent
|
||||
float: left
|
||||
height: 100%
|
||||
margin: 0 8px 0 0
|
||||
width: 30px
|
||||
|
||||
.logo-text
|
||||
display: inline-block
|
||||
font-family: $logoFontFamily
|
||||
font-size: 14px
|
||||
font-weight: $logoFontWeight
|
||||
theme('color', '$textPrimaryColor')
|
||||
line-height: 30px
|
||||
|
||||
a.header-menu
|
||||
theme('color', '$textPrimaryColor')
|
||||
|
||||
.header-options
|
||||
font-size: 13px
|
||||
|
||||
.menu-toggle
|
||||
display: inline-block
|
||||
height: 18px
|
||||
|
||||
.research-use
|
||||
font-size: 13px
|
||||
theme('color', '$textSecondaryColor')
|
||||
font-weight: bold
|
||||
|
||||
.btn
|
||||
theme('color', '$textSecondaryColor')
|
||||
cursor: pointer
|
||||
font-size: 13px
|
||||
font-weight: 500
|
||||
line-height: 26px
|
||||
|
||||
&:hover
|
||||
theme('color', '$hoverColor')
|
||||
|
||||
&:active
|
||||
theme('color', '$activeColor')
|
||||
|
||||
.studyListLinkSection
|
||||
theme('border-left', '%s solid $uiBorderColor' % $uiBorderThickness)
|
||||
margin: 3px 0 0 10px
|
||||
padding: 0 0 0 10px
|
||||
|
||||
&.header-big
|
||||
padding-left: $studyListPadding
|
||||
padding-right: $studyListPadding
|
||||
|
||||
.brand
|
||||
height: 100%
|
||||
line-height: $topBarExpandedHeight - 10px
|
||||
|
||||
.logo-image
|
||||
margin: 0 20px 0 0
|
||||
width: 50px
|
||||
|
||||
.logo-text
|
||||
font-size: 30px
|
||||
|
||||
.studyListLinkSection
|
||||
border: none
|
||||
left: 0
|
||||
margin: 0
|
||||
padding: 0
|
||||
position: absolute
|
||||
top: 0
|
||||
@ -1,13 +0,0 @@
|
||||
<template name="toolbarSection">
|
||||
<div class="toolbarSection noselect p-x-2">
|
||||
<div class="clearfix">
|
||||
<div class="pull-left m-t-1">
|
||||
{{>roundedButtonGroup leftSidebarToggleButtonData}}
|
||||
</div>
|
||||
{{>toolbarSectionTools toolbarButtons=toolbarButtons}}
|
||||
<div class="pull-right m-t-1 rm-x-1">
|
||||
{{>roundedButtonGroup rightSidebarToggleButtonData}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,322 +0,0 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import $ from 'jquery';
|
||||
|
||||
import { OHIF } from 'ohif-core';
|
||||
import 'meteor/ohif:viewerbase';
|
||||
|
||||
function isThereSeries(studies) {
|
||||
if (studies.length === 1) {
|
||||
const study = studies[0];
|
||||
|
||||
if (study.seriesList && study.seriesList.length > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (study.displaySets && study.displaySets.length > 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Template.toolbarSection.onCreated(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
if (OHIF.uiSettings.leftSidebarOpen && isThereSeries(instance.data.studies)) {
|
||||
instance.data.state.set('leftSidebar', 'studies');
|
||||
}
|
||||
});
|
||||
|
||||
Template.toolbarSection.helpers({
|
||||
leftSidebarToggleButtonData() {
|
||||
const instance = Template.instance();
|
||||
return {
|
||||
toggleable: true,
|
||||
key: 'leftSidebar',
|
||||
value: instance.data.state,
|
||||
options: [{
|
||||
value: 'studies',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-studies',
|
||||
svgWidth: 15,
|
||||
svgHeight: 13,
|
||||
bottomLabel: 'Series'
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
rightSidebarToggleButtonData() {
|
||||
const instance = Template.instance();
|
||||
|
||||
// TODO: Figured out a way to handle both right panel contents
|
||||
// return {
|
||||
// toggleable: true,
|
||||
// key: 'rightSidebar',
|
||||
// value: instance.data.state,
|
||||
// options: [{
|
||||
// value: 'hangingprotocols',
|
||||
// iconClasses: 'fa fa-cog',
|
||||
// bottomLabel: 'Hanging'
|
||||
// }]
|
||||
// };
|
||||
|
||||
return {
|
||||
toggleable: true,
|
||||
key: 'rightSidebar',
|
||||
value: instance.data.state,
|
||||
options: [{
|
||||
value: 'measurements',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-measurements-lesions',
|
||||
svgWidth: 18,
|
||||
svgHeight: 10,
|
||||
bottomLabel: 'Measurements'
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
toolbarButtons() {
|
||||
const extraTools = [];
|
||||
|
||||
extraTools.push({
|
||||
id: 'crosshairs',
|
||||
title: 'Crosshairs',
|
||||
classes: 'imageViewerTool',
|
||||
iconClasses: 'fa fa-crosshairs'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'magnify',
|
||||
title: 'Magnify',
|
||||
classes: 'imageViewerTool toolbarSectionButton',
|
||||
iconClasses: 'fa fa-circle'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'wwwcRegion',
|
||||
title: 'ROI Window',
|
||||
classes: 'imageViewerTool',
|
||||
iconClasses: 'fa fa-square'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'dragProbe',
|
||||
title: 'Probe',
|
||||
classes: 'imageViewerTool',
|
||||
iconClasses: 'fa fa-dot-circle-o'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'ellipticalRoi',
|
||||
title: 'Ellipse',
|
||||
classes: 'imageViewerTool',
|
||||
iconClasses: 'fa fa-circle-o'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'rectangleRoi',
|
||||
title: 'Rectangle',
|
||||
classes: 'imageViewerTool',
|
||||
iconClasses: 'fa fa-square-o'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'toggleDownloadDialog',
|
||||
title: 'Download',
|
||||
classes: 'imageViewerCommand',
|
||||
iconClasses: 'fa fa-camera',
|
||||
active: () => $('#downloadDialog').is(':visible')
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'invert',
|
||||
title: 'Invert',
|
||||
classes: 'imageViewerCommand',
|
||||
iconClasses: 'fa fa-adjust'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'rotateR',
|
||||
title: 'Rotate Right',
|
||||
classes: 'imageViewerCommand',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-rotate-right'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'flipH',
|
||||
title: 'Flip H',
|
||||
classes: 'imageViewerCommand',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-flip-horizontal'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'flipV',
|
||||
title: 'Flip V',
|
||||
classes: 'imageViewerCommand',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-flip-vertical'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'clearTools',
|
||||
title: 'Clear',
|
||||
classes: 'imageViewerCommand',
|
||||
iconClasses: 'fa fa-trash'
|
||||
});
|
||||
|
||||
const buttonData = [];
|
||||
|
||||
buttonData.push({
|
||||
id: 'stackScroll',
|
||||
title: 'Stack Scroll',
|
||||
classes: 'imageViewerTool',
|
||||
iconClasses: 'fa fa-bars'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'zoom',
|
||||
title: 'Zoom',
|
||||
classes: 'imageViewerTool',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-zoom'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'wwwc',
|
||||
title: 'Levels',
|
||||
classes: 'imageViewerTool',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-levels'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'pan',
|
||||
title: 'Pan',
|
||||
classes: 'imageViewerTool',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-pan'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'length',
|
||||
title: 'Length',
|
||||
classes: 'imageViewerTool toolbarSectionButton',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-temp'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'annotate',
|
||||
title: 'Annotate',
|
||||
classes: 'imageViewerTool',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-non-target'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'angle',
|
||||
title: 'Angle',
|
||||
classes: 'imageViewerTool',
|
||||
iconClasses: 'fa fa-angle-left'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'resetViewport',
|
||||
title: 'Reset',
|
||||
classes: 'imageViewerCommand',
|
||||
iconClasses: 'fa fa-undo'
|
||||
});
|
||||
|
||||
if (!OHIF.uiSettings.displayEchoUltrasoundWorkflow) {
|
||||
|
||||
buttonData.push({
|
||||
id: 'previousDisplaySet',
|
||||
title: 'Previous',
|
||||
classes: 'imageViewerCommand',
|
||||
iconClasses: 'fa fa-toggle-up fa-fw'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'nextDisplaySet',
|
||||
title: 'Next',
|
||||
classes: 'imageViewerCommand',
|
||||
iconClasses: 'fa fa-toggle-down fa-fw'
|
||||
});
|
||||
|
||||
const { isPlaying } = OHIF.viewerbase.viewportUtils;
|
||||
buttonData.push({
|
||||
id: 'toggleCinePlay',
|
||||
title: () => isPlaying() ? 'Stop' : 'Play',
|
||||
classes: 'imageViewerCommand',
|
||||
iconClasses: () => ('fa fa-fw ' + (isPlaying() ? 'fa-stop' : 'fa-play')),
|
||||
active: isPlaying
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'toggleCineDialog',
|
||||
title: 'CINE',
|
||||
classes: 'imageViewerCommand',
|
||||
iconClasses: 'fa fa-youtube-play',
|
||||
active: () => $('#cineDialog').is(':visible')
|
||||
});
|
||||
}
|
||||
|
||||
buttonData.push({
|
||||
id: 'layout',
|
||||
title: 'Layout',
|
||||
iconClasses: 'fa fa-th-large',
|
||||
buttonTemplateName: 'layoutButton'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'toggleMore',
|
||||
title: 'More',
|
||||
classes: 'rp-x-1 rm-l-3',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-more',
|
||||
subTools: extraTools
|
||||
});
|
||||
|
||||
return buttonData;
|
||||
},
|
||||
|
||||
hangingProtocolButtons() {
|
||||
let buttonData = [];
|
||||
|
||||
buttonData.push({
|
||||
id: 'previousPresentationGroup',
|
||||
title: 'Prev. Stage',
|
||||
iconClasses: 'fa fa-step-backward',
|
||||
buttonTemplateName: 'previousPresentationGroupButton'
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'nextPresentationGroup',
|
||||
title: 'Next Stage',
|
||||
iconClasses: 'fa fa-step-forward',
|
||||
buttonTemplateName: 'nextPresentationGroupButton'
|
||||
});
|
||||
|
||||
return buttonData;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Template.toolbarSection.onRendered(function() {
|
||||
const instance = Template.instance();
|
||||
|
||||
instance.$('#layout').dropdown();
|
||||
|
||||
if (OHIF.uiSettings.displayEchoUltrasoundWorkflow) {
|
||||
OHIF.viewerbase.viewportUtils.toggleCineDialog();
|
||||
}
|
||||
|
||||
// TODO: Figure out a way to disable/enable the buttons with each status from New API
|
||||
// // Set disabled/enabled tool buttons that are set in toolManager
|
||||
// const states = OHIF.viewerbase.toolManager.getToolDefaultStates();
|
||||
// const disabledToolButtons = states.disabledToolButtons;
|
||||
// const allToolbarButtons = $('#toolbar').find('button');
|
||||
// if (disabledToolButtons && disabledToolButtons.length > 0) {
|
||||
// for (let i = 0; i < allToolbarButtons.length; i++) {
|
||||
// const toolbarButton = allToolbarButtons[i];
|
||||
// $(toolbarButton).prop('disabled', false);
|
||||
//
|
||||
// const index = disabledToolButtons.indexOf($(toolbarButton).attr('id'));
|
||||
// if (index !== -1) {
|
||||
// $(toolbarButton).prop('disabled', true);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
});
|
||||
@ -1,13 +0,0 @@
|
||||
@require '{ohif:viewerbase}/app'
|
||||
|
||||
.toolbarSection
|
||||
theme('border-bottom', '%s solid $uiBorderColor' % $uiBorderThickness)
|
||||
flex: 0 0 auto
|
||||
height: $toolbarHeight
|
||||
padding-top: 6px
|
||||
position: relative
|
||||
transition(height 300ms ease)
|
||||
width: 100%
|
||||
|
||||
&.expanded
|
||||
height: $toolbarHeight + $toolbarDrawerHeight
|
||||
@ -1,107 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { OHIF } from 'ohif-core';
|
||||
import { MeasurementTable } from 'meteor/ohif:measurement-table';
|
||||
|
||||
import { CineDialog } from 'react-viewerbase';
|
||||
import FlexboxLayout from '../flexboxLayout/flexboxLayout.js';
|
||||
|
||||
import 'meteor/ohif:cornerstone';
|
||||
import 'meteor/ohif:viewerbase';
|
||||
|
||||
|
||||
/**
|
||||
* Inits OHIF Hanging Protocol's onReady.
|
||||
* It waits for OHIF Hanging Protocol to be ready to instantiate the ProtocolEngine
|
||||
* Hanging Protocol will use OHIF LayoutManager to render viewports properly
|
||||
*/
|
||||
const initHangingProtocol = () => {
|
||||
// When Hanging Protocol is ready
|
||||
HP.ProtocolStore.onReady(() => {
|
||||
|
||||
// Gets all StudyMetadata objects: necessary for Hanging Protocol to access study metadata
|
||||
const studyMetadataList = OHIF.viewer.StudyMetadataList.all();
|
||||
|
||||
// Caches Layout Manager: Hanging Protocol uses it for layout management according to current protocol
|
||||
const layoutManager = OHIF.viewerbase.layoutManager;
|
||||
|
||||
// Instantiate StudyMetadataSource: necessary for Hanging Protocol to get study metadata
|
||||
const studyMetadataSource = new OHIF.studies.classes.OHIFStudyMetadataSource();
|
||||
|
||||
// Get prior studies map
|
||||
const studyPriorsMap = OHIF.studylist.functions.getStudyPriorsMap(studyMetadataList);
|
||||
|
||||
// Creates Protocol Engine object with required arguments
|
||||
const ProtocolEngine = new HP.ProtocolEngine(layoutManager, studyMetadataList, studyPriorsMap, studyMetadataSource);
|
||||
|
||||
// Sets up Hanging Protocol engine
|
||||
HP.setEngine(ProtocolEngine);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
OHIF.viewer.defaultTool = {
|
||||
left: 'wwwc',
|
||||
right: 'zoom',
|
||||
middle: 'pan'
|
||||
};
|
||||
|
||||
OHIF.viewer.refLinesEnabled = true;
|
||||
OHIF.viewer.cine = {
|
||||
framesPerSecond: 24,
|
||||
loop: true
|
||||
};
|
||||
|
||||
const viewportUtils = OHIF.viewerbase.viewportUtils;
|
||||
|
||||
OHIF.viewer.functionList = {
|
||||
toggleCineDialog: viewportUtils.toggleCineDialog,
|
||||
toggleCinePlay: viewportUtils.toggleCinePlay,
|
||||
clearTools: viewportUtils.clearTools,
|
||||
resetViewport: viewportUtils.resetViewport,
|
||||
invert: viewportUtils.invert
|
||||
};
|
||||
|
||||
// Create the synchronizer used to update reference lines
|
||||
OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer('cornerstonenewimage', cornerstoneTools.updateImageSynchronizer);
|
||||
|
||||
OHIF.viewer.metadataProvider = new OHIF.cornerstone.MetadataProvider();
|
||||
|
||||
// Metadata configuration
|
||||
const metadataProvider = OHIF.viewer.metadataProvider;
|
||||
cornerstone.metaData.addProvider(metadataProvider.provider.bind(metadataProvider));
|
||||
|
||||
// Instantiate viewer plugins
|
||||
OHIF.viewer.measurementTable = new MeasurementTable();
|
||||
|
||||
class Viewer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
leftSidebar: 'studies',
|
||||
rightSidebar: 'measurements',
|
||||
studies: this.props.studies
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<>
|
||||
<div className='viewerDialogs'>
|
||||
{/*<CineDialog/>*/}
|
||||
</div>
|
||||
<div id="viewer" className='Viewer'>
|
||||
{/*<ToolbarSection/>*/}
|
||||
<FlexboxLayout studies={this.state.studies}/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Viewer.propTypes = {
|
||||
studies: PropTypes.array
|
||||
};
|
||||
|
||||
export default Viewer;
|
||||
@ -1,13 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
body
|
||||
background-color: black
|
||||
|
||||
#viewer
|
||||
background-color: black
|
||||
height: 100%
|
||||
width: 100%
|
||||
|
||||
.loadingTextDiv
|
||||
theme('color', '$textSecondaryColor')
|
||||
font-size: 30px
|
||||
@ -1,45 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { OHIF } from 'ohif-core';
|
||||
import { cornerstoneWADOImageLoader } from 'meteor/ohif:cornerstone';
|
||||
import sha from './sha.js';
|
||||
import version from './version.js';
|
||||
|
||||
OHIF.info = {
|
||||
sha,
|
||||
version
|
||||
};
|
||||
|
||||
Meteor.startup(function() {
|
||||
const maxWebWorkers = Math.max(navigator.hardwareConcurrency - 1, 1);
|
||||
const config = {
|
||||
maxWebWorkers: maxWebWorkers,
|
||||
startWebWorkersOnDemand: true,
|
||||
webWorkerPath: OHIF.utils.absoluteUrl('packages/ohif_cornerstone/public/js/cornerstoneWADOImageLoaderWebWorker.es5.js'),
|
||||
taskConfiguration: {
|
||||
decodeTask: {
|
||||
loadCodecsOnStartup: true,
|
||||
initializeCodecsOnStartup: false,
|
||||
codecsPath: OHIF.utils.absoluteUrl('packages/ohif_cornerstone/public/js/cornerstoneWADOImageLoaderCodecs.es5.js'),
|
||||
usePDFJS: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cornerstoneWADOImageLoader.webWorkerManager.initialize(config);
|
||||
|
||||
cornerstoneWADOImageLoader.configure({
|
||||
beforeSend: function(xhr) {
|
||||
const headers = OHIF.DICOMWeb.getAuthorizationHeader();
|
||||
|
||||
if (headers.Authorization) {
|
||||
xhr.setRequestHeader("Authorization", headers.Authorization);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (Meteor.settings &&
|
||||
Meteor.settings.public &&
|
||||
Meteor.settings.public.clientOnly === true) {
|
||||
Meteor.disconnect();
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OHIF Viewer</title>
|
||||
<meta name="description" content="Open Health Imaging Foundation DICOM Viewer">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no">
|
||||
<link rel="shortcut icon" type="image/png" href="/images/logo.png" sizes="16x16 32x32 64x64">
|
||||
<!--Best display for mobile devices-->
|
||||
<meta http-equiv="cleartype" content="on">
|
||||
<meta name="MobileOptimized" content="320">
|
||||
<meta name="HandheldFriendly" content="True">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
@ -1,3 +0,0 @@
|
||||
import loglevel from 'loglevel';
|
||||
log = loglevel.getLogger('OHIFViewer');
|
||||
log.setLevel('info');
|
||||
5797
OHIFViewer/package-lock.json
generated
5797
OHIFViewer/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,35 +0,0 @@
|
||||
{
|
||||
"name": "ohifviewer",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.2.0",
|
||||
"bootstrap": "^4.1.3",
|
||||
"jquery": "3.3.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"loglevel": "^1.6.1",
|
||||
"popper": "^1.0.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.6.3",
|
||||
"react-dom": "^16.6.3",
|
||||
"react-redux": "^6.0.0",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-viewerbase": "^0.1.2",
|
||||
"redux": "^4.0.1",
|
||||
"underscore": "1.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"husky": "^1.2.0",
|
||||
"puppeteer": "^1.5.0"
|
||||
},
|
||||
"scripts": {
|
||||
"version": "node -p -e \"'export default \\'' + require('./package.json').version + '\\';'\" > client/version.js",
|
||||
"sha": "echo \"export default '\"$(git rev-parse HEAD)\"';\" > client/sha.js",
|
||||
"details": "npm run version && npm run sha",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@ -1 +0,0 @@
|
||||
Subproject commit a728166da104670b9d62ba4cd8ec4eef645ff29e
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 1519ec03b2aca15efc1291d945ac1139c966bc78
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit a97200c7af92ca37d55cec13f65cf593e3d6b8dc
|
||||
@ -1 +0,0 @@
|
||||
Subproject commit 09b6fb77ae73ee7927bf7bd4324925aef38d4242
|
||||
@ -1,7 +0,0 @@
|
||||
active-entry
|
||||
hipaa-audit-log
|
||||
meteor-stale-session
|
||||
orthanc-remote
|
||||
ohif-cornerstone
|
||||
ohif-viewerbase/client/compatibility
|
||||
ohif-core/client/lib/third-party/
|
||||
@ -1,47 +0,0 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"allowImportExportEverywhere": true
|
||||
},
|
||||
"plugins": [
|
||||
"meteor"
|
||||
],
|
||||
"env": {
|
||||
/* Allows global vars from the Meteor environment to pass and enables certain rules */
|
||||
"meteor": true,
|
||||
"node": true,
|
||||
"browser": true
|
||||
},
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"plugin:meteor/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"import/resolver": "meteor"
|
||||
},
|
||||
"rules": {
|
||||
"meteor/no-session": 0, // We are actually using Session for now...
|
||||
"meteor/eventmap-params": [2, {"eventParamName": "event"}],
|
||||
"meteor/eventmap-params": [2, {"templateInstanceParamName": "instance"}],
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"import/no-unresolved": 0, // There are a bunch of ESLint problems with Meteor's resolver
|
||||
"import/no-duplicates": 0, // So we are disabling these for now
|
||||
"import/extensions": 0,
|
||||
"import/no-absolute-path": 0,
|
||||
"no-console": 0, // For development
|
||||
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
|
||||
"indent": ["error", 4],
|
||||
"new-cap": 0, // Until Match has an exception
|
||||
"func-names": 0, // This is a bit of an annoying rule
|
||||
"no-underscore-dangle": 0, // Doesn't seem to mesh with _id for MongoDB Ids (or SimpleSchema)
|
||||
"max-len": 0, // TODO: re-enable the rules below and fix all of the errors
|
||||
"consistent-return": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-mixed-operators": 0,
|
||||
"arrow-body-style": 0,
|
||||
"valid-typeof": 0,
|
||||
"import/prefer-default-export": 0
|
||||
//"no-undef": 0
|
||||
},
|
||||
"globals": {}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import Hammer from 'hammerjs';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneMath from 'cornerstone-math';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||
import dicomParser from 'dicom-parser';
|
||||
import * as dcmjs from 'dcmjs/build/dcmjs';
|
||||
|
||||
cornerstoneTools.external.Hammer = Hammer;
|
||||
cornerstoneTools.external.cornerstone = cornerstone;
|
||||
cornerstoneTools.external.cornerstoneMath = cornerstoneMath;
|
||||
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
|
||||
cornerstoneWADOImageLoader.external.dicomParser = dicomParser;
|
||||
|
||||
// Export scripts that will populate the Cornerstone namespace as a side effect only import.
|
||||
// This is effectively the public API...
|
||||
export {
|
||||
cornerstone,
|
||||
cornerstoneTools,
|
||||
cornerstoneMath,
|
||||
cornerstoneWADOImageLoader,
|
||||
dicomParser,
|
||||
dcmjs
|
||||
};
|
||||
@ -1,34 +0,0 @@
|
||||
Package.describe({
|
||||
name: 'ohif:cornerstone',
|
||||
summary: 'Cornerstone Web-based Medical Imaging libraries',
|
||||
version: '0.0.1'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
hammerjs: '2.0.8',
|
||||
'cornerstone-core': '2.2.8',
|
||||
'cornerstone-tools': '3.0.0-b.1471',
|
||||
'cornerstone-math': '0.1.7',
|
||||
'dicom-parser': '1.8.3',
|
||||
'cornerstone-wado-image-loader': '2.2.3',
|
||||
'dcmjs': '0.2.1'
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.7');
|
||||
|
||||
api.use('ecmascript');
|
||||
|
||||
api.addAssets('public/js/cornerstoneWADOImageLoaderCodecs.es5.js', 'client');
|
||||
api.addAssets('public/js/cornerstoneWADOImageLoaderWebWorker.es5.js', 'client');
|
||||
api.addAssets('public/js/cornerstoneWADOImageLoaderWebWorker.min.js.map', 'client');
|
||||
|
||||
api.mainModule('main.js', 'client');
|
||||
|
||||
api.export('cornerstone', 'client');
|
||||
api.export('cornerstoneMath', 'client');
|
||||
api.export('cornerstoneTools', 'client');
|
||||
api.export('cornerstoneWADOImageLoader', 'client');
|
||||
api.export('dicomParser', 'client');
|
||||
api.export('dcmjs', 'client');
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 75 75" version="1.1" x="0px" y="0px"><g><path style="" d="M 21.484375 11.132812 C 21.484375 16.777344 16.90625 21.355469 11.261719 21.355469 C 5.617188 21.355469 1.039062 16.777344 1.039062 11.132812 C 1.039062 5.488281 5.617188 0.910156 11.261719 0.910156 C 16.90625 0.910156 21.484375 5.488281 21.484375 11.132812 Z M 21.484375 11.132812 " stroke="none" fill-rule="nonzero" fill="rgb(0%,0%,0%)" fill-opacity="1"></path><path style="" d="M 47.722656 11.132812 C 47.722656 16.777344 43.144531 21.355469 37.5 21.355469 C 31.855469 21.355469 27.277344 16.777344 27.277344 11.132812 C 27.277344 5.488281 31.855469 0.910156 37.5 0.910156 C 43.144531 0.910156 47.722656 5.488281 47.722656 11.132812 Z M 47.722656 11.132812 " stroke="none" fill-rule="nonzero" fill="rgb(0%,0%,0%)" fill-opacity="1"></path><path style="" d="M 74.21875 11.132812 C 74.21875 16.777344 69.640625 21.355469 63.996094 21.355469 C 58.351562 21.355469 53.773438 16.777344 53.773438 11.132812 C 53.773438 5.488281 58.351562 0.910156 63.996094 0.910156 C 69.640625 0.910156 74.21875 5.488281 74.21875 11.132812 Z M 74.21875 11.132812 " stroke="none" fill-rule="nonzero" fill="rgb(0%,0%,0%)" fill-opacity="1"></path><path style="" d="M 21.355469 37.5 C 21.355469 43.144531 16.777344 47.71875 11.132812 47.71875 C 5.488281 47.71875 0.910156 43.144531 0.910156 37.5 C 0.910156 31.851562 5.488281 27.277344 11.132812 27.277344 C 16.777344 27.277344 21.355469 31.851562 21.355469 37.5 Z M 21.355469 37.5 " stroke="none" fill-rule="nonzero" fill="rgb(0%,0%,0%)" fill-opacity="1"></path><path style="" d="M 47.59375 37.5 C 47.59375 43.144531 43.015625 47.71875 37.371094 47.71875 C 31.726562 47.71875 27.148438 43.144531 27.148438 37.5 C 27.148438 31.851562 31.726562 27.277344 37.371094 27.277344 C 43.015625 27.277344 47.59375 31.851562 47.59375 37.5 Z M 47.59375 37.5 " stroke="none" fill-rule="nonzero" fill="rgb(0%,0%,0%)" fill-opacity="1"></path><path style="" d="M 74.089844 37.5 C 74.089844 43.144531 69.511719 47.71875 63.867188 47.71875 C 58.222656 47.71875 53.644531 43.144531 53.644531 37.5 C 53.644531 31.851562 58.222656 27.277344 63.867188 27.277344 C 69.511719 27.277344 74.089844 31.851562 74.089844 37.5 Z M 74.089844 37.5 " stroke="none" fill-rule="nonzero" fill="rgb(0%,0%,0%)" fill-opacity="1"></path><path style="" d="M 21.484375 63.214844 C 21.484375 68.859375 16.90625 73.4375 11.261719 73.4375 C 5.617188 73.4375 1.039062 68.859375 1.039062 63.214844 C 1.039062 57.570312 5.617188 52.992188 11.261719 52.992188 C 16.90625 52.992188 21.484375 57.570312 21.484375 63.214844 Z M 21.484375 63.214844 " stroke="none" fill-rule="nonzero" fill="rgb(0%,0%,0%)" fill-opacity="1"></path><path style="" d="M 47.722656 63.214844 C 47.722656 68.859375 43.144531 73.4375 37.5 73.4375 C 31.855469 73.4375 27.277344 68.859375 27.277344 63.214844 C 27.277344 57.570312 31.855469 52.992188 37.5 52.992188 C 43.144531 52.992188 47.722656 57.570312 47.722656 63.214844 Z M 47.722656 63.214844 " stroke="none" fill-rule="nonzero" fill="rgb(0%,0%,0%)" fill-opacity="1"></path><path style="" d="M 74.21875 63.214844 C 74.21875 68.859375 69.640625 73.4375 63.996094 73.4375 C 58.351562 73.4375 53.773438 68.859375 53.773438 63.214844 C 53.773438 57.570312 58.351562 52.992188 63.996094 52.992188 C 69.640625 52.992188 74.21875 57.570312 74.21875 63.214844 Z M 74.21875 63.214844 " stroke="none" fill-rule="nonzero" fill="rgb(0%,0%,0%)" fill-opacity="1"></path></g></svg>
|
||||
|
Before Width: | Height: | Size: 3.4 KiB |
@ -1,243 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { OHIF } from 'ohif-core';
|
||||
|
||||
// Local imports
|
||||
import { removeFromArray } from '../lib/removeFromArray';
|
||||
|
||||
/**
|
||||
* This class represents a Hanging Protocol at the highest level
|
||||
*
|
||||
* @type {Protocol}
|
||||
*/
|
||||
HP.Protocol = class Protocol {
|
||||
/**
|
||||
* The Constructor for the Class to create a Protocol with the bare
|
||||
* minimum information
|
||||
*
|
||||
* @param name The desired name for the Protocol
|
||||
*/
|
||||
constructor(name) {
|
||||
// Create a new UUID for this Protocol
|
||||
this.id = OHIF.utils.guid();
|
||||
|
||||
// Store a value which determines whether or not a Protocol is locked
|
||||
// This is probably temporary, since we will eventually have role / user
|
||||
// checks for editing. For now we just need it to prevent changes to the
|
||||
// default protocols.
|
||||
this.locked = false;
|
||||
|
||||
// Boolean value to indicate if the protocol has updated priors information
|
||||
// it's set in "updateNumberOfPriorsReferenced" function
|
||||
this.hasUpdatedPriorsInformation = false;
|
||||
|
||||
// Apply the desired name
|
||||
this.name = name;
|
||||
|
||||
// Set the created and modified dates to Now
|
||||
this.createdDate = new Date();
|
||||
this.modifiedDate = new Date();
|
||||
|
||||
// If we are logged in while creating this Protocol,
|
||||
// store this information as well
|
||||
if (OHIF.user && OHIF.user.userLoggedIn && OHIF.user.userLoggedIn()) {
|
||||
this.createdBy = OHIF.user.getUserId();
|
||||
this.modifiedBy = OHIF.user.getUserId();
|
||||
}
|
||||
|
||||
// Create two empty Sets specifying which roles
|
||||
// have read and write access to this Protocol
|
||||
this.availableTo = new Set();
|
||||
this.editableBy = new Set();
|
||||
|
||||
// Define empty arrays for the Protocol matching rules
|
||||
// and Stages
|
||||
this.protocolMatchingRules = [];
|
||||
this.stages = [];
|
||||
|
||||
// Define auxiliary values for priors
|
||||
this.numberOfPriorsReferenced = -1;
|
||||
}
|
||||
|
||||
getNumberOfPriorsReferenced(skipCache = false) {
|
||||
let numberOfPriorsReferenced = skipCache !== true ? this.numberOfPriorsReferenced : -1;
|
||||
|
||||
// Check if information is cached already
|
||||
if (numberOfPriorsReferenced > -1) {
|
||||
return numberOfPriorsReferenced;
|
||||
}
|
||||
|
||||
numberOfPriorsReferenced = 0;
|
||||
|
||||
// Search each study matching rule for prior rules
|
||||
// Each stage can have many viewports that can have
|
||||
// multiple study matching rules.
|
||||
this.stages.forEach(stage => {
|
||||
if (!stage.viewports) {
|
||||
return;
|
||||
}
|
||||
|
||||
stage.viewports.forEach(viewport => {
|
||||
if (!viewport.studyMatchingRules) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewport.studyMatchingRules.forEach(rule => {
|
||||
// If the current rule is not a priors rule, it will return -1 then numberOfPriorsReferenced will continue to be 0
|
||||
const priorsReferenced = rule.getNumberOfPriorsReferenced();
|
||||
if (priorsReferenced > numberOfPriorsReferenced) {
|
||||
numberOfPriorsReferenced = priorsReferenced;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.numberOfPriorsReferenced = numberOfPriorsReferenced;
|
||||
|
||||
return numberOfPriorsReferenced
|
||||
}
|
||||
|
||||
updateNumberOfPriorsReferenced() {
|
||||
this.getNumberOfPriorsReferenced(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to update the modifiedDate when the Protocol
|
||||
* has been changed
|
||||
*/
|
||||
protocolWasModified() {
|
||||
// If we are logged in while modifying this Protocol,
|
||||
// store this information as well
|
||||
if (OHIF.user && OHIF.user.userLoggedIn && OHIF.user.userLoggedIn()) {
|
||||
this.modifiedBy = OHIF.user.getUserId();
|
||||
}
|
||||
|
||||
// Protocol has been modified, so mark priors information
|
||||
// as "outdated"
|
||||
this.hasUpdatedPriorsInformation = false;
|
||||
|
||||
// Update number of priors referenced info
|
||||
this.updateNumberOfPriorsReferenced();
|
||||
|
||||
// Update the modifiedDate with the current Date/Time
|
||||
this.modifiedDate = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Protocol class needs to be instantiated from a JavaScript Object
|
||||
* containing the Protocol data. This function fills in a Protocol with the Object
|
||||
* data.
|
||||
*
|
||||
* @param input A Protocol as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// Check if the input already has an ID
|
||||
// If so, keep it. It not, create a new UUID
|
||||
this.id = input.id || OHIF.utils.guid();
|
||||
|
||||
// Assign the input name to the Protocol
|
||||
this.name = input.name;
|
||||
|
||||
// Retrieve locked status, use !! to make it truthy
|
||||
// so that undefined values will be set to false
|
||||
this.locked = !!input.locked;
|
||||
|
||||
// TODO: Check how to regenerate Set from Object
|
||||
//this.availableTo = new Set(input.availableTo);
|
||||
//this.editableBy = new Set(input.editableBy);
|
||||
|
||||
// If the input contains Protocol matching rules
|
||||
if (input.protocolMatchingRules) {
|
||||
input.protocolMatchingRules.forEach(ruleObject => {
|
||||
// Create new Rules from the stored data
|
||||
var rule = new HP.ProtocolMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
|
||||
// Add them to the Protocol
|
||||
this.protocolMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If the input contains data for various Stages in the
|
||||
// display set sequence
|
||||
if (input.stages) {
|
||||
input.stages.forEach(stageObject => {
|
||||
// Create Stages from the stored data
|
||||
var stage = new HP.Stage();
|
||||
stage.fromObject(stageObject);
|
||||
|
||||
// Add them to the Protocol
|
||||
this.stages.push(stage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of the current Protocol with a new name
|
||||
*
|
||||
* @param name
|
||||
* @returns {Protocol|*}
|
||||
*/
|
||||
createClone(name) {
|
||||
// Create a new JavaScript independent of the current Protocol
|
||||
var currentProtocol = Object.assign({}, this);
|
||||
|
||||
// Create a new Protocol to return
|
||||
var clonedProtocol = new HP.Protocol();
|
||||
|
||||
// Apply the desired properties
|
||||
currentProtocol.id = clonedProtocol.id;
|
||||
clonedProtocol.fromObject(currentProtocol);
|
||||
|
||||
// If we have specified a name, assign it
|
||||
if (name) {
|
||||
clonedProtocol.name = name;
|
||||
}
|
||||
|
||||
// Unlock the clone
|
||||
clonedProtocol.locked = false;
|
||||
|
||||
// Return the cloned Protocol
|
||||
return clonedProtocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Stage to this Protocol's display set sequence
|
||||
*
|
||||
* @param stage
|
||||
*/
|
||||
addStage(stage) {
|
||||
this.stages.push(stage);
|
||||
|
||||
// Update the modifiedDate and User that last
|
||||
// modified this Protocol
|
||||
this.protocolWasModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Rule to this Protocol's array of matching rules
|
||||
*
|
||||
* @param rule
|
||||
*/
|
||||
addProtocolMatchingRule(rule) {
|
||||
this.protocolMatchingRules.push(rule);
|
||||
|
||||
// Update the modifiedDate and User that last
|
||||
// modified this Protocol
|
||||
this.protocolWasModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a Rule from this Protocol's array of matching rules
|
||||
*
|
||||
* @param rule
|
||||
*/
|
||||
removeProtocolMatchingRule(rule) {
|
||||
var wasRemoved = removeFromArray(this.protocolMatchingRules, rule);
|
||||
|
||||
// Update the modifiedDate and User that last
|
||||
// modified this Protocol
|
||||
if (wasRemoved) {
|
||||
this.protocolWasModified();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,172 +0,0 @@
|
||||
import { OHIF } from 'ohif-core';
|
||||
import { comparators } from '../lib/comparators';
|
||||
|
||||
const EQUALS_REGEXP = /^equals$/;
|
||||
|
||||
/**
|
||||
* This Class represents a Rule to be evaluated given a set of attributes
|
||||
* Rules have:
|
||||
* - An attribute (e.g. 'seriesDescription')
|
||||
* - A constraint Object, in the form required by Validate.js:
|
||||
*
|
||||
* rule.constraint = {
|
||||
* contains: {
|
||||
* value: 'T-1'
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* Note: In this example we use the 'contains' Validator, which is a custom Validator defined in Viewerbase
|
||||
*
|
||||
* - A value for whether or not they are Required to be matched (default: False)
|
||||
* - A value for their relative weighting during Protocol or Image matching (default: 1)
|
||||
*/
|
||||
export class Rule {
|
||||
/**
|
||||
* The Constructor for the Class to create a Rule with the bare
|
||||
* minimum information
|
||||
*
|
||||
* @param name The desired name for the Rule
|
||||
*/
|
||||
constructor(attribute, constraint, required, weight) {
|
||||
// Create a new UUID for this Rule
|
||||
this.id = OHIF.utils.guid();
|
||||
|
||||
// Set the Rule's weight (defaults to 1)
|
||||
this.weight = weight || 1;
|
||||
|
||||
// If an attribute is specified, assign it
|
||||
if (attribute) {
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
// If a constraint is specified, assign it
|
||||
if (constraint) {
|
||||
this.constraint = constraint;
|
||||
}
|
||||
|
||||
// If a value for 'required' is specified, assign it
|
||||
if (required === undefined) {
|
||||
// If no value was specified, default to False
|
||||
this.required = false;
|
||||
} else {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
// Cache for constraint info object
|
||||
this._constraintInfo = void 0;
|
||||
|
||||
// Cache for validator and value object
|
||||
this._validatorAndValue = void 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Rule class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a Protocol with the Object data.
|
||||
*
|
||||
* @param input A Rule as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// Check if the input already has an ID
|
||||
// If so, keep it. It not, create a new UUID
|
||||
this.id = input.id || OHIF.utils.guid();
|
||||
|
||||
// Assign the specified input data to the Rule
|
||||
this.required = input.required;
|
||||
this.weight = input.weight;
|
||||
this.attribute = input.attribute;
|
||||
this.constraint = input.constraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the constraint info object for the current constraint
|
||||
* @return {Object\undefined} Constraint object or undefined if current constraint
|
||||
* is not valid or not found in comparators list
|
||||
*/
|
||||
getConstraintInfo() {
|
||||
let constraintInfo = this._constraintInfo;
|
||||
// Check if info is cached already
|
||||
if (constraintInfo !== void 0) {
|
||||
return constraintInfo;
|
||||
}
|
||||
|
||||
const ruleConstraint = Object.keys(this.constraint)[0];
|
||||
|
||||
if (ruleConstraint !== void 0) {
|
||||
constraintInfo = comparators.find(comparator => ruleConstraint === comparator.id)
|
||||
}
|
||||
|
||||
// Cache this information for later use
|
||||
this._constraintInfo = constraintInfo;
|
||||
|
||||
return constraintInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current rule is related to priors
|
||||
* @return {Boolean} True if a rule is related to priors or false otherwise
|
||||
*/
|
||||
isRuleForPrior() {
|
||||
// @TODO: Should we check this too? this.attribute === 'relativeTime'
|
||||
return this.attribute === 'abstractPriorValue';
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current rule is a rule for priors, returns the number of referenced priors. Otherwise, returns -1.
|
||||
* @return {Number} The number of referenced priors or -1 if not applicable. Returns zero if the actual value could not be determined.
|
||||
*/
|
||||
getNumberOfPriorsReferenced() {
|
||||
if (!this.isRuleForPrior()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get rule's validator and value
|
||||
const ruleValidatorAndValue = this.getConstraintValidatorAndValue();
|
||||
const { value, validator } = ruleValidatorAndValue;
|
||||
const intValue = parseInt(value, 10) || 0; // avoid possible NaN
|
||||
|
||||
// "Equal to" validators
|
||||
if (EQUALS_REGEXP.test(validator)) {
|
||||
// In this case, -1 (the oldest prior) indicates that at least one study is used
|
||||
return intValue < 0 ? 1 : intValue;
|
||||
}
|
||||
|
||||
// Default cases return value
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the constraint validator and value
|
||||
* @return {Object|undefined} Returns an object containing the validator and it's value or undefined
|
||||
*/
|
||||
getConstraintValidatorAndValue() {
|
||||
let validatorAndValue = this._validatorAndValue;
|
||||
|
||||
// Check if validator and value are cached already
|
||||
if (validatorAndValue !== void 0) {
|
||||
return validatorAndValue;
|
||||
}
|
||||
|
||||
// Get the constraint info object
|
||||
const constraintInfo = this.getConstraintInfo();
|
||||
|
||||
// Constraint info object exists and is valid
|
||||
if (constraintInfo !== void 0) {
|
||||
const validator = constraintInfo.validator;
|
||||
const currentValidator = this.constraint[validator];
|
||||
|
||||
if (currentValidator) {
|
||||
const constraintValidator = constraintInfo.validatorOption;
|
||||
const constraintValue = currentValidator[constraintValidator];
|
||||
|
||||
validatorAndValue = {
|
||||
value: constraintValue,
|
||||
validator: constraintInfo.id
|
||||
};
|
||||
|
||||
this._validatorAndValue = validatorAndValue;
|
||||
}
|
||||
}
|
||||
|
||||
return validatorAndValue;
|
||||
}
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
import { OHIF } from 'ohif-core';
|
||||
|
||||
/**
|
||||
* A Stage is one step in the Display Set Sequence for a Hanging Protocol
|
||||
*
|
||||
* Stages are defined as a ViewportStructure and an array of Viewports
|
||||
*
|
||||
* @type {Stage}
|
||||
*/
|
||||
HP.Stage = class Stage {
|
||||
constructor(ViewportStructure, name) {
|
||||
// Create a new UUID for this Stage
|
||||
this.id = OHIF.utils.guid();
|
||||
|
||||
// Assign the name and ViewportStructure provided
|
||||
this.name = name;
|
||||
this.viewportStructure = ViewportStructure;
|
||||
|
||||
// Create an empty array for the Viewports
|
||||
this.viewports = [];
|
||||
|
||||
// Set the created date to Now
|
||||
this.createdDate = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a clone of the current Stage with a new name
|
||||
*
|
||||
* Note! This method absolutely cannot be renamed 'clone', because
|
||||
* Minimongo's insert method uses 'clone' internally and this
|
||||
* somehow causes very bizarre behaviour
|
||||
*
|
||||
* @param name
|
||||
* @returns {Stage|*}
|
||||
*/
|
||||
createClone(name) {
|
||||
// Create a new JavaScript independent of the current Protocol
|
||||
var currentStage = Object.assign({}, this);
|
||||
|
||||
// Create a new Stage to return
|
||||
var clonedStage = new HP.Stage();
|
||||
|
||||
// Assign the desired properties
|
||||
currentStage.id = clonedStage.id;
|
||||
clonedStage.fromObject(currentStage);
|
||||
|
||||
// If we have specified a name, assign it
|
||||
if (name) {
|
||||
clonedStage.name = name;
|
||||
}
|
||||
|
||||
// Return the cloned Stage
|
||||
return clonedStage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Stage class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a Protocol with the Object data.
|
||||
*
|
||||
* @param input A Stage as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// Check if the input already has an ID
|
||||
// If so, keep it. It not, create a new UUID
|
||||
this.id = input.id || OHIF.utils.guid();
|
||||
|
||||
// Assign the input name to the Stage
|
||||
this.name = input.name;
|
||||
|
||||
// If a ViewportStructure is present in the input, add it from the
|
||||
// input data
|
||||
this.viewportStructure = new HP.ViewportStructure();
|
||||
this.viewportStructure.fromObject(input.viewportStructure);
|
||||
|
||||
// If any viewports are present in the input object
|
||||
if (input.viewports) {
|
||||
input.viewports.forEach(viewportObject => {
|
||||
// Create a new Viewport with their data
|
||||
var viewport = new HP.Viewport();
|
||||
viewport.fromObject(viewportObject);
|
||||
|
||||
// Add it to the viewports array
|
||||
this.viewports.push(viewport);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,81 +0,0 @@
|
||||
// Local imports
|
||||
import { removeFromArray } from '../lib/removeFromArray';
|
||||
|
||||
/**
|
||||
* This Class defines a Viewport in the Hanging Protocol Stage. A Viewport contains
|
||||
* arrays of Rules that are matched in the ProtocolEngine in order to determine which
|
||||
* images should be hung.
|
||||
*
|
||||
* @type {Viewport}
|
||||
*/
|
||||
HP.Viewport = class Viewport {
|
||||
constructor() {
|
||||
this.viewportSettings = {};
|
||||
this.imageMatchingRules = [];
|
||||
this.seriesMatchingRules = [];
|
||||
this.studyMatchingRules = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the Viewport class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a Viewport with the Object data.
|
||||
*
|
||||
* @param input The Viewport as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
// If ImageMatchingRules exist, create them from the Object data
|
||||
// and add them to the Viewport's imageMatchingRules array
|
||||
if (input.imageMatchingRules) {
|
||||
input.imageMatchingRules.forEach(ruleObject => {
|
||||
var rule = new HP.ImageMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
this.imageMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If SeriesMatchingRules exist, create them from the Object data
|
||||
// and add them to the Viewport's seriesMatchingRules array
|
||||
if (input.seriesMatchingRules) {
|
||||
input.seriesMatchingRules.forEach(ruleObject => {
|
||||
var rule = new HP.SeriesMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
this.seriesMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If StudyMatchingRules exist, create them from the Object data
|
||||
// and add them to the Viewport's studyMatchingRules array
|
||||
if (input.studyMatchingRules) {
|
||||
input.studyMatchingRules.forEach(ruleObject => {
|
||||
var rule = new HP.StudyMatchingRule();
|
||||
rule.fromObject(ruleObject);
|
||||
this.studyMatchingRules.push(rule);
|
||||
});
|
||||
}
|
||||
|
||||
// If ViewportSettings exist, add them to the current protocol
|
||||
if (input.viewportSettings) {
|
||||
this.viewportSettings = input.viewportSettings;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and removes a rule from whichever array it exists in.
|
||||
* It is not required to specify if it exists in studyMatchingRules,
|
||||
* seriesMatchingRules, or imageMatchingRules
|
||||
*
|
||||
* @param rule
|
||||
*/
|
||||
removeRule(rule) {
|
||||
var array;
|
||||
if (rule instanceof HP.StudyMatchingRule) {
|
||||
array = this.studyMatchingRules;
|
||||
} else if (rule instanceof HP.SeriesMatchingRule) {
|
||||
array = this.seriesMatchingRules;
|
||||
} else if (rule instanceof HP.ImageMatchingRule) {
|
||||
array = this.imageMatchingRules;
|
||||
}
|
||||
|
||||
removeFromArray(array, rule);
|
||||
}
|
||||
};
|
||||
@ -1,53 +0,0 @@
|
||||
/**
|
||||
* The ViewportStructure class represents the layout and layout properties that
|
||||
* Viewports are displayed in. ViewportStructure has a type, which corresponds to
|
||||
* a layout template, and a set of properties, which depend on the type.
|
||||
*
|
||||
* @type {ViewportStructure}
|
||||
*/
|
||||
HP.ViewportStructure = class ViewportStructure {
|
||||
constructor(type, properties) {
|
||||
this.type = type;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Occasionally the ViewportStructure class needs to be instantiated from a JavaScript Object.
|
||||
* This function fills in a ViewportStructure with the Object data.
|
||||
*
|
||||
* @param input The ViewportStructure as a JavaScript Object, e.g. retrieved from MongoDB or JSON
|
||||
*/
|
||||
fromObject(input) {
|
||||
this.type = input.type;
|
||||
this.properties = input.properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the layout template name based on the layout type
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getLayoutTemplateName() {
|
||||
// Viewport structure can be updated later when we build more complex display layouts
|
||||
switch (this.type) {
|
||||
case 'grid':
|
||||
return 'gridLayout';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of Viewports required for this layout
|
||||
* given the layout type and properties
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
getNumViewports() {
|
||||
// Viewport structure can be updated later when we build more complex display layouts
|
||||
switch (this.type) {
|
||||
case 'grid':
|
||||
// For the typical grid layout, we only need to multiply rows by columns to
|
||||
// obtain the number of viewports
|
||||
return this.properties.rows * this.properties.columns;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,9 +0,0 @@
|
||||
import { Rule } from '../Rule';
|
||||
|
||||
/**
|
||||
* The ImageMatchingRule class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {ImageMatchingRule}
|
||||
*/
|
||||
HP.ImageMatchingRule = class ImageMatchingRule extends Rule {};
|
||||
@ -1,9 +0,0 @@
|
||||
import { Rule } from '../Rule';
|
||||
|
||||
/**
|
||||
* The ProtocolMatchingRule Class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {ProtocolMatchingRule}
|
||||
*/
|
||||
HP.ProtocolMatchingRule = class ProtocolMatchingRule extends Rule {};
|
||||
@ -1,9 +0,0 @@
|
||||
import { Rule } from '../Rule';
|
||||
|
||||
/**
|
||||
* The SeriesMatchingRule Class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {SeriesMatchingRule}
|
||||
*/
|
||||
HP.SeriesMatchingRule = class SeriesMatchingRule extends Rule {};
|
||||
@ -1,9 +0,0 @@
|
||||
import { Rule } from '../Rule';
|
||||
|
||||
/**
|
||||
* The StudyMatchingRule Class extends the Rule Class.
|
||||
*
|
||||
* At present it does not add any new methods or attributes
|
||||
* @type {StudyMatchingRule}
|
||||
*/
|
||||
HP.StudyMatchingRule = class StudyMatchingRule extends Rule {};
|
||||
@ -1,15 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { comparators } from './lib/comparators';
|
||||
|
||||
MatchedProtocols = new Meteor.Collection(null);
|
||||
MatchedProtocols._debugName = 'MatchedProtocols';
|
||||
|
||||
HangingProtocols = new Meteor.Collection(null);
|
||||
HangingProtocols._debugName = 'HangingProtocols';
|
||||
|
||||
Comparators = new Meteor.Collection(null);
|
||||
Comparators._debugName = 'Comparators';
|
||||
|
||||
comparators.forEach(item => {
|
||||
Comparators.insert(item);
|
||||
});
|
||||
@ -1,26 +0,0 @@
|
||||
<template name="matchedProtocols">
|
||||
<div id='matchedProtocols' class="btn-group">
|
||||
{{ #if matchedProtocols }}
|
||||
<button type="button" class="btn btn-sm dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
title="Select Hanging Protocol">
|
||||
<i class="fa fa-list-ol"></i>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<h5>Select a Hanging Protocol</h5>
|
||||
</li>
|
||||
{{ #each matchedProtocols }}
|
||||
<li>
|
||||
<a class='matchedProtocol {{#if active}}active{{/if}}'>
|
||||
{{name}}
|
||||
</a>
|
||||
</li>
|
||||
{{ /each }}
|
||||
</ul>
|
||||
{{ /if }}
|
||||
</div>
|
||||
</template>
|
||||
@ -1,20 +0,0 @@
|
||||
import { OHIF } from 'ohif-core';
|
||||
|
||||
Template.matchedProtocols.helpers({
|
||||
/**
|
||||
* Reactively re-render the MatchedProtocols Collection contents
|
||||
*/
|
||||
matchedProtocols() {
|
||||
return MatchedProtocols.find();
|
||||
}
|
||||
});
|
||||
|
||||
Template.matchedProtocols.events({
|
||||
/**
|
||||
* Instruct the ProtocolEngine to apply the specified Hanging Protocol
|
||||
*/
|
||||
'click .matchedProtocol': function() {
|
||||
var protocol = this;
|
||||
ProtocolEngine.setHangingProtocol(protocol);
|
||||
}
|
||||
});
|
||||
@ -1,14 +0,0 @@
|
||||
#matchedProtocols
|
||||
ul.dropdown-menu
|
||||
li
|
||||
a
|
||||
cursor: pointer
|
||||
|
||||
&:selected
|
||||
color: #4fbfff
|
||||
|
||||
h5
|
||||
display: block;
|
||||
padding: 3px 20px;
|
||||
clear: both;
|
||||
line-height: 1.42857143;
|
||||
@ -1,19 +0,0 @@
|
||||
<template name="nextPresentationGroupButton">
|
||||
<div id="{{this.id}}"
|
||||
class="toolbarSectionButton rp-x-1 {{this.classes}} {{activeClass}} {{ #if nextNotAvailable }}disabled{{ /if }}"
|
||||
title="{{this.title}}"
|
||||
disabled="{{ #if nextNotAvailable }}true{{ /if }}">
|
||||
<div class="svgContainer">
|
||||
{{ #if this.svgLink }}
|
||||
<svg>
|
||||
<use xlink:href={{absoluteUrl this.svgLink}}></use>
|
||||
</svg>
|
||||
{{ else }}
|
||||
<i class={{iconClasses}}></i>
|
||||
{{ /if }}
|
||||
</div>
|
||||
<div class="buttonLabel">
|
||||
{{this.title}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,43 +0,0 @@
|
||||
Template.nextPresentationGroupButton.helpers({
|
||||
/**
|
||||
* Check if a later stage exists for the user to switch to
|
||||
*
|
||||
* @returns {boolean} Whether or not a later stage exists
|
||||
*/
|
||||
nextNotAvailable() {
|
||||
// Run this helper whenever the ProtocolEngine / LayoutManager has changed
|
||||
|
||||
// If no ProtocolEngine has been defined yet, stop here
|
||||
if (!ProtocolEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return whether or not the current stage is the last stage
|
||||
return ProtocolEngine.stage === ProtocolEngine.getNumProtocolStages() - 1;
|
||||
}
|
||||
});
|
||||
|
||||
Template.nextPresentationGroupButton.events({
|
||||
/**
|
||||
* Switch to the next Presentation group
|
||||
*
|
||||
* @param event The click event on the button
|
||||
*/
|
||||
'click #nextPresentationGroup'(event) {
|
||||
// If no ProtocolEngine has been defined yet, do nothing
|
||||
if (!ProtocolEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop here if the tool is disabled
|
||||
if ($(event.currentTarget).hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide the button's Bootstrap tooltip in case it was shown
|
||||
$(event.currentTarget).tooltip('hide');
|
||||
|
||||
// Instruct the ProtocolEngine to switch to the next stage
|
||||
ProtocolEngine.nextProtocolStage();
|
||||
}
|
||||
});
|
||||
@ -1,19 +0,0 @@
|
||||
<template name="previousPresentationGroupButton">
|
||||
<div id="{{this.id}}"
|
||||
class="toolbarSectionButton rp-x-1 {{this.classes}} {{activeClass}} {{ #if previousNotAvailable }}disabled{{ /if }}"
|
||||
title="{{this.title}}"
|
||||
disabled="{{ #if previousNotAvailable }}true{{ /if }}">
|
||||
<div class="svgContainer">
|
||||
{{ #if this.svgLink }}
|
||||
<svg>
|
||||
<use xlink:href={{absoluteUrl this.svgLink}}></use>
|
||||
</svg>
|
||||
{{ else }}
|
||||
<i class={{iconClasses}}></i>
|
||||
{{ /if }}
|
||||
</div>
|
||||
<div class="buttonLabel">
|
||||
{{this.title}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,43 +0,0 @@
|
||||
Template.previousPresentationGroupButton.helpers({
|
||||
/**
|
||||
* Check if an earlier stage exists for the user to switch to
|
||||
*
|
||||
* @returns {boolean} Whether or not an earlier stage exists
|
||||
*/
|
||||
previousNotAvailable() {
|
||||
// Run this helper whenever the ProtocolEngine / LayoutManager has changed
|
||||
|
||||
// If no ProtocolEngine has been defined yet, stop here
|
||||
if (!ProtocolEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return whether or not the current stage is the first stage
|
||||
return ProtocolEngine.stage === 0;
|
||||
}
|
||||
});
|
||||
|
||||
Template.previousPresentationGroupButton.events({
|
||||
/**
|
||||
* Switch to the previous Presentation group
|
||||
*
|
||||
* @param event The click event on the button
|
||||
*/
|
||||
'click #previousPresentationGroup'(event) {
|
||||
// If no ProtocolEngine has been defined yet, do nothing
|
||||
if (!ProtocolEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop here if the tool is disabled
|
||||
if ($(event.currentTarget).hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide the button's Bootstrap tooltip in case it was shown
|
||||
$(event.currentTarget).tooltip('hide');
|
||||
|
||||
// Instruct the ProtocolEngine to switch to the next stage
|
||||
ProtocolEngine.previousProtocolStage();
|
||||
}
|
||||
});
|
||||
@ -1,126 +0,0 @@
|
||||
<template name="protocolEditor">
|
||||
<div id='protocolEditor'>
|
||||
<div class="row navigationButtonsContainer">
|
||||
<div class="navigationButtons col-xs-12">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="active">
|
||||
<a data-toggle="tab" href="#protocolRulePane">
|
||||
Protocol
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" href="#activeViewportEditor">
|
||||
Active Viewport
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div id="protocolRulePane" class="tab-pane active">
|
||||
{{#with activeProtocol}}
|
||||
<div id="selectProtocol" class="protocolEditorSection">
|
||||
<select id="protocolSelect" style="width: 50%">
|
||||
</select>
|
||||
<div class="dropdown protocolDropdown">
|
||||
<button type="button" class="btn btn-sm dropdown-toggle" id="protocolDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-cog"></i> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="protocolDropdown">
|
||||
<li>
|
||||
<a id="newProtocol">
|
||||
New <i class="fa fa-file-text"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="importProtocol" class="btn-file">
|
||||
Import <input type="file"> <i class="fa fa-upload"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{ #unless locked }}
|
||||
<li>
|
||||
<a id="renameProtocol">
|
||||
Rename <i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{ /unless }}
|
||||
<li>
|
||||
<a id="saveAsProtocol">
|
||||
Save As <i class="fa fa-files-o"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a id="exportJSON">
|
||||
Export <i class="fa fa-download"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{ #unless locked }}
|
||||
<li>
|
||||
<a id="deleteProtocol">
|
||||
Delete <i class="fa fa-trash-o"></i>
|
||||
</a>
|
||||
</li>
|
||||
{{ /unless }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="editProtocol" class="protocolEditorSection">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<p>Created: {{formatJSDate createdDate}}{{ #if createdBy }} by {{getUsername createdBy}}{{/if}}</p>
|
||||
<p>Last modified: {{formatJSDate modifiedDate}}{{ #if createdBy }} by {{getUsername modifiedBy}}{{/if}}</p>
|
||||
<p>Number of Priors Referenced: {{numberOfPriorsReferenced}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3>Protocol Applicability</h3>
|
||||
<hr/>
|
||||
{{ >ruleTable level="protocol" rules=protocolMatchingRules attributes=studyAttributes}}
|
||||
</div>
|
||||
<div class="row">
|
||||
<h3>Stages</h3>
|
||||
<hr/>
|
||||
{{ >stageSortable stages=stages}}
|
||||
</div>
|
||||
</div>
|
||||
{{/with}}
|
||||
</div>
|
||||
|
||||
<div id="activeViewportEditor" class="tab-pane">
|
||||
{{ #if activeViewportUndefined }}
|
||||
<div class="noActiveViewport">
|
||||
<h2>Select a Viewport</h2>
|
||||
<hr/>
|
||||
<p>
|
||||
Upon selecting a Viewport, this section will show a list of rules that can be used
|
||||
to determine which images will populate the specified Viewport. You can specify these
|
||||
rules to be evaluated at the study, series, or image level.
|
||||
</p>
|
||||
</div>
|
||||
{{ /if }}
|
||||
{{#with activeStage}}
|
||||
{{ >stageDetails }}
|
||||
{{/with}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#with activeProtocol}}
|
||||
<div id="protocolOptions" class="protocolEditorSection">
|
||||
{{ #if locked }}
|
||||
<button id="saveProtocol" type="button" class="btn btn-sm" disabled>
|
||||
Locked <i class="fa fa-lock" aria-hidden="true"></i>
|
||||
</button>
|
||||
{{ else }}
|
||||
<button id="saveProtocol" type="button" class="btn btn-sm">
|
||||
Save Changes <i class="fa fa-floppy-o" aria-hidden="true"></i>
|
||||
</button>
|
||||
{{ /if }}
|
||||
|
||||
{{#if modifiedDate}}
|
||||
<p class="lastSavedText">Last saved {{jsDateFromNow modifiedDate}}</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<a id="downloadElement" style="display:none;"></a>
|
||||
{{/with}}
|
||||
</div>
|
||||
</template>
|
||||
@ -1,384 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Template } from 'meteor/templating';
|
||||
import { Session } from 'meteor/session';
|
||||
import $ from 'jquery';
|
||||
|
||||
import { OHIF } from 'ohif-core';
|
||||
import 'meteor/ohif:viewerbase';
|
||||
|
||||
/**
|
||||
* Updates the Hanging Protocol Select2 Input
|
||||
*/
|
||||
function updateProtocolSelect() {
|
||||
if (!ProtocolEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through the available hanging protocols
|
||||
// to create an array with the protocols that includes
|
||||
// a property labelled 'text', so that Select2 has something
|
||||
// to display
|
||||
var protocolsSelect2Data = HP.ProtocolStore.getProtocol().map(function(protocol) {
|
||||
return {
|
||||
id: protocol.id,
|
||||
text: protocol.name
|
||||
};
|
||||
});
|
||||
|
||||
// Select the Protocol select DOM element
|
||||
var protocolSelect = $('#protocolSelect');
|
||||
|
||||
// Empty the element using Select2 for rerendering
|
||||
protocolSelect.select2().empty();
|
||||
|
||||
// Initialize the select element with Select2 using the
|
||||
// array of protocols
|
||||
protocolSelect.select2({
|
||||
data: protocolsSelect2Data
|
||||
});
|
||||
|
||||
// Update the ProtocolSelector to display the current active Protocol
|
||||
protocolSelect.select2().val(ProtocolEngine.protocol.id).trigger('change');
|
||||
}
|
||||
|
||||
Template.protocolEditor.onRendered(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
instance.timeAgoInterval = Meteor.setInterval(() => {
|
||||
// Run this every minute
|
||||
Session.set('timeAgoVariable', new Date());
|
||||
}, 60000);
|
||||
|
||||
// Update the Protocol select box when the hanging protocol store is ready
|
||||
HP.ProtocolStore.onReady(() => {
|
||||
updateProtocolSelect();
|
||||
});
|
||||
});
|
||||
|
||||
Template.protocolEditor.onDestroyed(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
Meteor.clearInterval(instance.timeAgoInterval);
|
||||
});
|
||||
|
||||
Template.protocolEditor.helpers({
|
||||
/**
|
||||
* Reactively updates the active Protocol
|
||||
*
|
||||
* @returns {*} The currently active Protocol Model
|
||||
*/
|
||||
activeProtocol() {
|
||||
// Whenever the Layout Manager is updated, trigger this helper
|
||||
|
||||
// If no ProtocolEngine, protocol, or stage is defined, stop here
|
||||
if (!ProtocolEngine ||
|
||||
!ProtocolEngine.protocol ||
|
||||
!ProtocolEngine.LayoutManager ||
|
||||
ProtocolEngine.stage === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Protocol Select box
|
||||
updateProtocolSelect();
|
||||
|
||||
// Make sure that the number of referenced priors is correct
|
||||
ProtocolEngine.protocol.updateNumberOfPriorsReferenced();
|
||||
|
||||
// Otherwise, return the active Hanging Protocol
|
||||
return ProtocolEngine.protocol;
|
||||
},
|
||||
/**
|
||||
* Reactively updates the active Protocol Stage
|
||||
*
|
||||
* @returns {*} The current Protocol's active Stage model
|
||||
*/
|
||||
activeStage() {
|
||||
// Whenever the Layout Manager is updated, trigger this helper
|
||||
|
||||
|
||||
// If no ProtocolEngine, protocol, or stage is defined, stop here
|
||||
if (!ProtocolEngine ||
|
||||
!ProtocolEngine.protocol ||
|
||||
!ProtocolEngine.LayoutManager ||
|
||||
ProtocolEngine.stage === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the Stage Model for the current Protocol's active Stage
|
||||
var stage = ProtocolEngine.getCurrentStageModel();
|
||||
if (!stage) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update active Stage's layout template and properties based on the displayed
|
||||
// layout properties. This is used to update the Stage Model when the user modifies
|
||||
// the layout in the viewer
|
||||
stage.viewportStructure.layoutTemplateName = ProtocolEngine.LayoutManager.layoutTemplateName;
|
||||
stage.viewportStructure.properties = ProtocolEngine.LayoutManager.layoutProps;
|
||||
|
||||
// If there is a discrepancy between the Stage's number of viewports and the
|
||||
// the number of required viewports given the properties above, rectify it
|
||||
// by removing or adding Viewports to the stage
|
||||
//
|
||||
// First, calculate the difference, if any exists
|
||||
var difference = stage.viewportStructure.getNumViewports() - stage.viewports.length;
|
||||
|
||||
if (difference < 0) {
|
||||
// Make the viewport difference into a positive value
|
||||
var absDifference = Math.abs(difference);
|
||||
|
||||
// If there are more Viewports defined than necessary, remove the extraneous Viewports
|
||||
var position = stage.viewports.length - absDifference;
|
||||
|
||||
// Splice extra viewports from the Stage's viewports array
|
||||
stage.viewports.splice(position, absDifference);
|
||||
} else if (difference > 0) {
|
||||
// If there are less Viewports defined than necessary, add viewports until we reach the
|
||||
// required amount
|
||||
|
||||
// Count up until the difference in number of Viewports
|
||||
for (var i = 0; i < difference; i++) {
|
||||
// Instantiate a new Viewport Model
|
||||
var viewport = new HP.Viewport();
|
||||
|
||||
// Add new Viewports to the Stage's viewports array
|
||||
stage.viewports.push(viewport);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the current Stage model for the active Protocol
|
||||
return ProtocolEngine.getCurrentStageModel();
|
||||
},
|
||||
activeViewportUndefined() {
|
||||
const viewportIndex = window.store.getState().viewports.activeViewport;
|
||||
return (viewportIndex === undefined);
|
||||
}
|
||||
});
|
||||
|
||||
Template.protocolEditor.events({
|
||||
/**
|
||||
* Creates a new Hanging Protocol and displays it in the Viewer
|
||||
*/
|
||||
'click #newProtocol'() {
|
||||
// Clone the default Protocol
|
||||
var protocol = HP.defaultProtocol.createClone();
|
||||
|
||||
// Change the Protocol name to state that it is New, and give it a timestamp
|
||||
protocol.name = 'New (created ' + moment().format('h:mm:ss a') + ')';
|
||||
|
||||
// Change the Protocol ID from the default value
|
||||
protocol.id = OHIF.utils.guid();
|
||||
|
||||
// Insert the protocol
|
||||
HP.ProtocolStore.addProtocol(protocol);
|
||||
|
||||
// Activate the new Protocol using the ProtocolEngine
|
||||
ProtocolEngine.setHangingProtocol(protocol);
|
||||
|
||||
// Update the protocol selector to display the new Protocols
|
||||
updateProtocolSelect();
|
||||
},
|
||||
/**
|
||||
* Rename the current Protocol
|
||||
*/
|
||||
'click #renameProtocol'() {
|
||||
var selectedProtocol = this;
|
||||
if (selectedProtocol.locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Define some details for the text entry dialog
|
||||
var title = 'Rename Protocol';
|
||||
var instructions = 'Enter a new name';
|
||||
var currentValue = selectedProtocol.name;
|
||||
|
||||
// Open the text entry dialog with the details above
|
||||
// and fire the callback function when finished.
|
||||
openTextEntryDialog(title, instructions, currentValue, function(value) {
|
||||
// Update the name with the entered text
|
||||
selectedProtocol.name = value;
|
||||
|
||||
// Update the protocol
|
||||
HP.ProtocolStore.updateProtocol(selectedProtocol.id, selectedProtocol);
|
||||
|
||||
// Update the protocol selector
|
||||
updateProtocolSelect();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Import a Protocol
|
||||
*/
|
||||
'click #importProtocol'() {
|
||||
// Hide the protocol dropdown manually, because it is not hidden automatically
|
||||
// when it has input as a child
|
||||
$("#protocolDropdown").dropdown('toggle');
|
||||
},
|
||||
/**
|
||||
* Triggers a custom event when for the HTML5 File input when files are selected
|
||||
*
|
||||
* @param event The Change event for the input
|
||||
*/
|
||||
'change .btn-file :file': function(event) {
|
||||
// http://www.abeautifulsite.net/whipping-file-inputs-into-shape-with-bootstrap-3/
|
||||
|
||||
// Find the Input in the DOM
|
||||
var input = $(event.currentTarget);
|
||||
|
||||
// Get the number of selected files
|
||||
var numFiles = input.get(0).files ? input.get(0).files.length : 1;
|
||||
|
||||
// Get the label of the file
|
||||
var label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
|
||||
|
||||
// Trigger our custom event with the number of files and label
|
||||
input.trigger('fileselect', [numFiles, label]);
|
||||
},
|
||||
/**
|
||||
* Imports files selected by the user into the Hanging Protocols Collection
|
||||
*
|
||||
* @param event The custom fileselect event
|
||||
*/
|
||||
'fileselect .btn-file :file': function(event) {
|
||||
// Retreieve the FileList
|
||||
var files = event.target.files;
|
||||
|
||||
// Create an HTML5 File Reader
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
var protocolToImport = JSON.parse(reader.result);
|
||||
|
||||
// Insert the protocol
|
||||
HP.ProtocolStore.addProtocol(protocolToImport);
|
||||
|
||||
// Update the protocol selector to display the imported Protocol
|
||||
updateProtocolSelect();
|
||||
};
|
||||
|
||||
// Instruct the FileReader to read the (first) selected file
|
||||
// TODO: Update to allow batch uploads?
|
||||
reader.readAsText(files[0], 'utf-8');
|
||||
},
|
||||
/**
|
||||
* Set the Hanging Protocol when the select box is changed
|
||||
*
|
||||
* @param event The select2:select event
|
||||
*/
|
||||
'select2:select #protocolSelect': function(event) {
|
||||
// Retrieve the protocolId
|
||||
var protocolId = event.params.data.id;
|
||||
|
||||
// Retrieve the protocol from the protocol store
|
||||
var selectedProtocol = HP.ProtocolStore.getProtocol(protocolId);
|
||||
|
||||
// If it doesn't exist, stop here
|
||||
if (!selectedProtocol) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the current Hanging Protocol to the user-specified Protocol
|
||||
ProtocolEngine.setHangingProtocol(selectedProtocol);
|
||||
},
|
||||
/**
|
||||
* Allow the Protocols / Stage navigation tabs to toggle the
|
||||
* 'active' class when clicked
|
||||
*/
|
||||
'click .navigationButtons a'() {
|
||||
$(this).addClass('active').siblings().removeClass('active');
|
||||
},
|
||||
/**
|
||||
* Update the protocol with the latest changes to the current Protocol
|
||||
*/
|
||||
'click #saveProtocol'() {
|
||||
var selectedProtocol = this;
|
||||
if (selectedProtocol.locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the Protocol's modifiedDate and modifiedBy User details
|
||||
selectedProtocol.protocolWasModified();
|
||||
|
||||
// Update the current Protocol in the database with the latest changes
|
||||
HP.ProtocolStore.updateProtocol(selectedProtocol.id, selectedProtocol);
|
||||
},
|
||||
/**
|
||||
* Save the current Protocol as a new document
|
||||
*/
|
||||
'click #saveAsProtocol'() {
|
||||
var selectedProtocol = this;
|
||||
|
||||
// Clone the selected Protocol
|
||||
var protocol = selectedProtocol.createClone();
|
||||
|
||||
// Define some details for the text entry dialog
|
||||
var title = 'Save Protocol As';
|
||||
var instructions = 'Enter a new name';
|
||||
var currentValue = protocol.name;
|
||||
|
||||
// Open the text entry dialog with the details above
|
||||
// and fire the callback function when finished.
|
||||
openTextEntryDialog(title, instructions, currentValue, function(value) {
|
||||
// Create a new ID for the protocol
|
||||
protocol.id = OHIF.utils.guid();
|
||||
|
||||
// Update the name with the entered text
|
||||
protocol.name = value;
|
||||
|
||||
// Unlock the protocol
|
||||
protocol.locked = false;
|
||||
|
||||
// Update the Protocol's modifiedDate and modifiedBy User details
|
||||
protocol.protocolWasModified();
|
||||
|
||||
// Insert the new Protocol
|
||||
HP.ProtocolStore.addProtocol(protocol);
|
||||
|
||||
// Activate the new Protocol using the ProtocolEngine
|
||||
ProtocolEngine.setHangingProtocol(protocol);
|
||||
|
||||
// Update the protocol selector to display the new Protocols
|
||||
updateProtocolSelect();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Export the currently selected Protocol as a JSON file
|
||||
*/
|
||||
'click #exportJSON'() {
|
||||
var selectedProtocol = this;
|
||||
|
||||
var protocolJSON = JSON.stringify(selectedProtocol, null, 2),
|
||||
currentDate = new Date(),
|
||||
filename = selectedProtocol.name + '-' + (currentDate.getTime().toString()) + '.json',
|
||||
protocolBlob = new Blob([protocolJSON], { type: 'application/json' });
|
||||
|
||||
var downloadElement = document.getElementById('downloadElement');
|
||||
downloadElement.href = URL.createObjectURL(protocolBlob);
|
||||
downloadElement.download = filename;
|
||||
downloadElement.click();
|
||||
},
|
||||
/**
|
||||
* Delete the currently selected Protocol
|
||||
*/
|
||||
'click #deleteProtocol'() {
|
||||
var selectedProtocol = this;
|
||||
if (selectedProtocol.locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
var options = {
|
||||
title: 'Delete Protocol',
|
||||
text: 'Are you sure you would like to remove this Protocol? This cannot be reversed.'
|
||||
};
|
||||
|
||||
OHIF.viewerbase.dialogUtils.showConfirmDialog(() => {
|
||||
// Remove the Protocol
|
||||
HP.ProtocolStore.removeProtocol(selectedProtocol.id);
|
||||
|
||||
// Reset the ProtocolEngine to the next best match
|
||||
ProtocolEngine.reset();
|
||||
|
||||
// Update the protocol selector
|
||||
updateProtocolSelect();
|
||||
}, options);
|
||||
}
|
||||
});
|
||||
@ -1,152 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
$height = 20px
|
||||
|
||||
#protocolEditor
|
||||
theme('background', '$primaryBackgroundColor')
|
||||
height: 100%
|
||||
width: 450px
|
||||
padding: 10px 0
|
||||
position: absolute
|
||||
top: 0
|
||||
|
||||
.navigationButtonsContainer
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
|
||||
.navigationButtons
|
||||
ul
|
||||
margin: 0 auto
|
||||
width: 300px
|
||||
|
||||
li
|
||||
text-align: center
|
||||
width: 150px
|
||||
margin: 0
|
||||
|
||||
&:first-child:not(:last-child)
|
||||
a
|
||||
border-top-right-radius: 0
|
||||
border-bottom-right-radius: 0
|
||||
|
||||
&:last-child:not(:first-child)
|
||||
a
|
||||
border-top-left-radius: 0
|
||||
border-bottom-left-radius: 0
|
||||
|
||||
a
|
||||
padding: 3px 9px
|
||||
cursor: pointer
|
||||
text-decoration: none
|
||||
outline: none
|
||||
theme('background-color', '$uiGrayDark')
|
||||
theme('border', '2px solid $uiBorderColorDark')
|
||||
theme('color', '$textSecondaryColor')
|
||||
transition($sidebarTransition)
|
||||
border-radius: $height
|
||||
|
||||
&.active a
|
||||
theme('background-color', '$activeColor')
|
||||
theme('border-color', '$uiBorderColorActive')
|
||||
theme('color', '$textColorActive')
|
||||
transition($sidebarTransition)
|
||||
|
||||
p
|
||||
h2
|
||||
h3
|
||||
h4
|
||||
theme('color', '$defaultColor')
|
||||
|
||||
label
|
||||
ul
|
||||
theme('color', '$defaultColor')
|
||||
font-weight: 400
|
||||
|
||||
label
|
||||
margin-right: 10px
|
||||
width: 25%
|
||||
|
||||
input[type='number']
|
||||
input[type='text']
|
||||
min-width: 50px
|
||||
width: 40%
|
||||
border: none
|
||||
background: #212121
|
||||
theme('color', '$defaultColor')
|
||||
text-align: center
|
||||
|
||||
button.btn
|
||||
background: #ffffff
|
||||
color: #3e3e3e
|
||||
|
||||
.tab-content
|
||||
height: calc(100% - 60px)
|
||||
overflow: auto;
|
||||
|
||||
.protocolEditorSection
|
||||
padding: 10px
|
||||
margin: 10px 0
|
||||
|
||||
#protocolOptions
|
||||
text-align: center
|
||||
position: absolute
|
||||
bottom: 0
|
||||
width: 100%
|
||||
margin-bottom: 0
|
||||
theme('background', '$uiGrayDarker')
|
||||
|
||||
p
|
||||
font-size: 8pt
|
||||
theme('color', '$defaultColor')
|
||||
margin: 0 10px
|
||||
display: inline-block
|
||||
|
||||
#editProtocol
|
||||
overflow-y: auto
|
||||
overflow-x: hidden
|
||||
padding-right: 16px
|
||||
padding-left: 20px
|
||||
margin-right: -16px
|
||||
width: 100%
|
||||
height: calc(100% - 50px)
|
||||
&::-webkit-scrollbar
|
||||
display: none
|
||||
|
||||
#selectProtocol
|
||||
text-align: center
|
||||
|
||||
.btn-file
|
||||
position: relative
|
||||
overflow: hidden
|
||||
|
||||
.btn-file input[type=file]
|
||||
position: absolute
|
||||
top: 0
|
||||
right: 0
|
||||
min-width: 100%
|
||||
min-height: 100%
|
||||
font-size: 100px
|
||||
text-align: right
|
||||
filter: 'alpha(opacity=0)'
|
||||
opacity: 0
|
||||
outline: none
|
||||
background: white
|
||||
cursor: inherit
|
||||
display: block
|
||||
|
||||
.protocolDropdown
|
||||
display: inline-block
|
||||
|
||||
ul
|
||||
li
|
||||
a
|
||||
cursor: pointer
|
||||
|
||||
#activeViewportEditor
|
||||
padding: 10px
|
||||
|
||||
.noActiveViewport
|
||||
padding: 40px
|
||||
|
||||
h3
|
||||
theme('color', '$defaultColor')
|
||||
@ -1,47 +0,0 @@
|
||||
<template name="ruleEntryDialog">
|
||||
<div class="ruleEntryDialog dialog-animated">
|
||||
<div class="dialogHeader">
|
||||
<h5>Rule Editor</h5>
|
||||
</div>
|
||||
<div class="dialogContent">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<select class="attributes" style="width: 95%;">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<select class="comparators" style="width: 95%;">
|
||||
{{ #each comparators }}
|
||||
<option value={{id}}>{{name}}</option>
|
||||
{{ /each }}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{{ #if ifTypeIs currentValue "string"}}
|
||||
<input class='currentValue' type="text" value={{currentValue}}>
|
||||
{{ /if }}
|
||||
|
||||
{{ #if ifTypeIs currentValue "undefined"}}
|
||||
<input class='currentValue' type="text" value={{currentValue}}>
|
||||
{{ /if }}
|
||||
|
||||
{{ #if ifTypeIs currentValue "number"}}
|
||||
<input class='currentValue' type="number" value={{currentValue}}>
|
||||
{{ /if }}
|
||||
|
||||
{{ #if ifTypeIs currentValue "boolean"}}
|
||||
<input class='currentValue' type="checkbox" {{valueIf currentValue "selected" ''}}>
|
||||
{{ /if }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialogFooter">
|
||||
<button id="cancel" class="btn btn-link" tabindex="1">Cancel</button>
|
||||
<button id="save" class="btn btn-primary" tabindex="0">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,420 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
import { Session } from 'meteor/session';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { Template } from 'meteor/templating';
|
||||
import { Blaze } from 'meteor/blaze';
|
||||
|
||||
import { OHIF } from 'ohif-core';
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
|
||||
const keys = {
|
||||
ESC: 27
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the specified dialog element and return browser
|
||||
* focus to the active viewport.
|
||||
*
|
||||
* @param dialog The DOM element of the dialog to close
|
||||
*/
|
||||
function closeHandler(dialog) {
|
||||
// Hide the lesion dialog
|
||||
$(dialog).css('display', 'none');
|
||||
|
||||
// Remove the backdrop
|
||||
$('.removableBackdrop').remove();
|
||||
|
||||
// Restore the focus to the active viewport
|
||||
Viewerbase.setFocusToActiveViewport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays and updates the UI of the Rule Entry Dialog given a new set of
|
||||
* attributes, the rule level (protocol, study, series, or instance), and an
|
||||
* optional rule to edit.
|
||||
*
|
||||
* @param attributes List of attributes the user can set
|
||||
* @param level Level of the Rule to create / edit
|
||||
* @param rule Optional Rule
|
||||
*/
|
||||
openRuleEntryDialog = function(attributes, level, rule) {
|
||||
// Get the lesion location dialog
|
||||
var dialog = $('.ruleEntryDialog');
|
||||
|
||||
// Clear any input that is still on the page
|
||||
var currentValueInput = dialog.find('input.currentValue');
|
||||
currentValueInput.val('');
|
||||
|
||||
// Store the Dialog DOM data, rule level and rule in the template data
|
||||
Template.ruleEntryDialog.dialog = dialog;
|
||||
Template.ruleEntryDialog.level = level;
|
||||
Template.ruleEntryDialog.rule = rule;
|
||||
|
||||
// Initialize the Select2 search box for the attribute list
|
||||
var attributeSelect = dialog.find('.attributes');
|
||||
attributeSelect.html('').select2({
|
||||
data: attributes,
|
||||
placeholder: 'Select an attribute',
|
||||
allowClear: true
|
||||
});
|
||||
|
||||
// If a rule has been provided, set the value of the attribute Select2 input
|
||||
// to the attribute set in the rule.
|
||||
if (rule && rule.attribute) {
|
||||
attributeSelect.val(rule.attribute);
|
||||
}
|
||||
|
||||
// Event data to be passed to the event handler
|
||||
let eventData;
|
||||
|
||||
// If a rule has been provided, use its constraint to find the relevant Comparator
|
||||
if (rule && rule.constraint) {
|
||||
var validator = Object.keys(rule.constraint)[0];
|
||||
var validatorOption = Object.keys(rule.constraint[validator])[0];
|
||||
var comparator = Comparators.findOne({
|
||||
validator: validator,
|
||||
validatorOption: validatorOption
|
||||
});
|
||||
|
||||
// Set the current value input based on the rule constraint
|
||||
var currentValue = rule.constraint[validator][validatorOption];
|
||||
currentValueInput.val(currentValue);
|
||||
|
||||
eventData = currentValue;
|
||||
|
||||
// If a Comparator was found, set the default value of the Comparators select2 box
|
||||
// to the comparatorId in the input rule
|
||||
if (comparator) {
|
||||
// Trigger('change') is used to update the Select2 choice in the UI
|
||||
dialog.find('.comparators').val(comparator.id).trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger('change') is used to update the Select2 choice in the UI and so
|
||||
// that the currentValue is updated based on the current attribute
|
||||
attributeSelect.trigger('change', eventData);
|
||||
|
||||
// Update the dialog's CSS so that it is visible on the page
|
||||
dialog.css('display', 'block');
|
||||
|
||||
// Show the backdrop
|
||||
Blaze.render(Template.removableBackdrop, document.body);
|
||||
|
||||
// Make sure the context menu is closed when the user clicks away
|
||||
$('.removableBackdrop').one('mousedown touchstart', function() {
|
||||
closeHandler(dialog);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the current active element's imageId using Cornerstone
|
||||
*/
|
||||
function getActiveViewportImageId() {
|
||||
const enabledElement = Viewerbase.viewportUtils.getEnabledElementForActiveElement();
|
||||
|
||||
if (!enabledElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return the enabled element's imageId
|
||||
return enabledElement.image.imageId;
|
||||
}
|
||||
|
||||
function getAbstractPriorValue(imageId) {
|
||||
// @TypeSafeStudies
|
||||
// Retrieves the first study of the collection using the given sort order.
|
||||
// Since we're only interrested in the first record, "null" will be used
|
||||
// as search criteria (thus no actual search will be made).
|
||||
const currentStudy = OHIF.viewer.Studies.findBy(null, {
|
||||
sort: [ ['studyDate', 'desc'] ]
|
||||
});
|
||||
|
||||
if (!currentStudy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const priorStudy = cornerstone.metaData.get('study', imageId);
|
||||
if (!priorStudy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const studies = OHIF.studylist.collections.Studies.find({
|
||||
patientId: currentStudy.patientId,
|
||||
studyDate: {
|
||||
$lt: currentStudy.studyDate
|
||||
}
|
||||
}, {
|
||||
sort: {
|
||||
studyDate: -1
|
||||
}
|
||||
});
|
||||
|
||||
let priorIndex = 0;
|
||||
|
||||
// TODO: Check what the abstract prior value should equal for an unrelated study?
|
||||
studies.forEach(function(study, index) {
|
||||
if (study.studyInstanceUid === priorStudy.studyInstanceUid) {
|
||||
// Abstract prior index starts from 1 in the DICOM standard
|
||||
// so we add 1 here
|
||||
priorIndex = index + 1;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return priorIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current value of a metadata tag or property. It searches the value in different levels (study, series or instance)
|
||||
* @param {String} tagOrProperty DICOM Tag or Property name (Ex: 'x00100020', 'patientId')
|
||||
* @return {Any} The value of the DICOM tag or property name
|
||||
*/
|
||||
const getCurrentTagOrPropertyValue = tagOrProperty => {
|
||||
// Retrieve the active viewport's imageId. If none exists, stop here
|
||||
const imageId = getActiveViewportImageId();
|
||||
if (!imageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tagOrProperty === 'abstractPriorValue') {
|
||||
return getAbstractPriorValue(imageId);
|
||||
}
|
||||
|
||||
// Create the object for the instance metadata
|
||||
let instance;
|
||||
|
||||
OHIF.viewer.StudyMetadataList.find(studyMetadata => {
|
||||
// Search for the instance that has the current imageId
|
||||
instance = studyMetadata.findInstance(instance => {
|
||||
return instance.getImageId() === imageId;
|
||||
});
|
||||
|
||||
// If instance if found stop the search
|
||||
return !!instance;
|
||||
});
|
||||
|
||||
// No instance found
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the value for the given tag
|
||||
// It searches the value in different levels (study, series or instance)
|
||||
const tagOrPropertyValue = instance.getTagValue(tagOrProperty);
|
||||
|
||||
// If not found, is a custom Hanging Protocol attribute
|
||||
if (tagOrPropertyValue === void 0) {
|
||||
return HP.attributeDefaults[tagOrProperty];
|
||||
}
|
||||
|
||||
return tagOrPropertyValue;
|
||||
};
|
||||
|
||||
Template.ruleEntryDialog.onCreated(function() {
|
||||
// Define the ReactiveVars that will be used to link aspects of the UI
|
||||
var template = this;
|
||||
// Note: currentValue's initial value must be a string so the template renders properly
|
||||
template.currentValue = new ReactiveVar('');
|
||||
template.attribute = new ReactiveVar();
|
||||
template.comparatorId = new ReactiveVar();
|
||||
});
|
||||
|
||||
Template.ruleEntryDialog.onRendered(function() {
|
||||
// Initialize the Comparators Select2 box
|
||||
var template = Template.instance();
|
||||
template.$('.comparators').select2();
|
||||
|
||||
// Get the default Comparator from the Select2 box and use it to
|
||||
// initialize the comparatorId ReactiveVar
|
||||
var comparatorId = template.$('.comparators').val();
|
||||
template.comparatorId.set(comparatorId);
|
||||
|
||||
const dialog = template.$('.ruleEntryDialog');
|
||||
dialog.draggable();
|
||||
});
|
||||
|
||||
Template.ruleEntryDialog.helpers({
|
||||
/**
|
||||
* Returns the Comparators Collection to the Template with reactive rerendering
|
||||
*/
|
||||
comparators: function() {
|
||||
return Comparators.find();
|
||||
},
|
||||
/**
|
||||
* Reactively updates the current value of the selected attribute for the selected image
|
||||
*
|
||||
* @returns {*} Attribute value for the active image
|
||||
*/
|
||||
currentValue: function() {
|
||||
return Template.instance().currentValue.get();
|
||||
}
|
||||
});
|
||||
|
||||
Template.ruleEntryDialog.events({
|
||||
/**
|
||||
* Save a rule that is being edited
|
||||
*
|
||||
* @param event the Click event
|
||||
* @param template The template context
|
||||
*/
|
||||
'click #save': function(event, template) {
|
||||
// Retrieve the input properties to the template
|
||||
var dialog = Template.ruleEntryDialog.dialog;
|
||||
var level = Template.ruleEntryDialog.level;
|
||||
|
||||
// Retrieve the current values for the attribute value and comparatorId
|
||||
var attribute = template.attribute.get();
|
||||
var comparatorId = template.comparatorId.get();
|
||||
var currentValue = template.currentValue.get();
|
||||
|
||||
// If currentValue input is undefined, prevent saving this rule
|
||||
if (currentValue === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we are editing a rule or creating a new one
|
||||
var rule;
|
||||
if (Template.ruleEntryDialog.rule) {
|
||||
// If we are editing a rule, change the rule data
|
||||
rule = Template.ruleEntryDialog.rule;
|
||||
} else {
|
||||
// If we are creating a rule, obtain the active Viewport model
|
||||
// from the Protocol and Stage
|
||||
var viewport = getActiveViewportModel();
|
||||
|
||||
// Create a rule depending on the level property of this dialog
|
||||
switch (level) {
|
||||
case 'protocol':
|
||||
rule = new HP.ProtocolMatchingRule();
|
||||
ProtocolEngine.protocol.addProtocolMatchingRule(rule);
|
||||
break;
|
||||
case 'study':
|
||||
rule = new HP.StudyMatchingRule();
|
||||
viewport.studyMatchingRules.push(rule);
|
||||
break;
|
||||
case 'series':
|
||||
rule = new HP.SeriesMatchingRule();
|
||||
viewport.seriesMatchingRules.push(rule);
|
||||
break;
|
||||
case 'instance':
|
||||
rule = new HP.ImageMatchingRule();
|
||||
viewport.imageMatchingRules.push(rule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the Comparator from the Comparators Collection given its ID
|
||||
var comparator = Comparators.findOne({
|
||||
id: comparatorId
|
||||
});
|
||||
|
||||
// Create a new constraint to add to the rule
|
||||
var constraint = {};
|
||||
constraint[comparator.validator] = {};
|
||||
constraint[comparator.validator][comparator.validatorOption] = currentValue;
|
||||
|
||||
// Set the attribute and constraint of the rule
|
||||
rule.attribute = attribute;
|
||||
rule.constraint = constraint;
|
||||
|
||||
// Instruct the Protocol Engine to update the Layout Manager with new data
|
||||
var viewportIndex = window.store.getState().viewports.activeViewport;
|
||||
ProtocolEngine.updateViewports(viewportIndex);
|
||||
|
||||
// Close the dialog
|
||||
closeHandler(dialog);
|
||||
},
|
||||
/**
|
||||
* Allow the user to click the Cancel button to close the dialog
|
||||
*/
|
||||
'click #cancel': function() {
|
||||
var dialog = Template.ruleEntryDialog.dialog;
|
||||
closeHandler(dialog);
|
||||
},
|
||||
/**
|
||||
* Allow Esc keydown events to close the dialog
|
||||
*
|
||||
* @param event The Keydown event details
|
||||
* @returns {boolean} Return false to prevent bubbling of the event
|
||||
*/
|
||||
'keydown .ruleEntryDialog': function(event) {
|
||||
var dialog = Template.ruleEntryDialog.dialog;
|
||||
|
||||
// If Esc key is pressed, close the dialog
|
||||
if (event.which === keys.ESC) {
|
||||
closeHandler(dialog);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update the currentValue ReactiveVar if the user changes the attribute
|
||||
*
|
||||
* @param event The Change event for the select box
|
||||
* @param template The current template context
|
||||
*/
|
||||
'change select.attributes'(event, template, currentValue) {
|
||||
// Obtain the user-specified attribute to test against
|
||||
const attribute = $(event.currentTarget).val();
|
||||
|
||||
// Store it in the ReactiveVar
|
||||
template.attribute.set(attribute);
|
||||
|
||||
// Store this attribute in the template data context
|
||||
Template.ruleEntryDialog.selectedAttribute = attribute;
|
||||
|
||||
// // Get the level of this dialog
|
||||
// var level = Template.ruleEntryDialog.level;
|
||||
|
||||
let value;
|
||||
|
||||
// Preset currentValue, use it
|
||||
if (currentValue) {
|
||||
value = currentValue;
|
||||
}
|
||||
else {
|
||||
// Retrieve the current value of the attribute for the active viewport model
|
||||
value = getCurrentTagOrPropertyValue(attribute);
|
||||
}
|
||||
|
||||
// Update the ReactiveVar with the user-specified value
|
||||
template.currentValue.set(value);
|
||||
|
||||
// Enforce to update the input value (Otherwise, ReactiveVar does not update input value with the same values)
|
||||
const currentValueInput = $('.ruleEntryDialog').find('input.currentValue');
|
||||
currentValueInput.val(value);
|
||||
},
|
||||
/**
|
||||
* Update the currentValue ReactiveVar if the user changes the attribute value
|
||||
*
|
||||
* @param event The Change event for the input
|
||||
* @param template The current template context
|
||||
*/
|
||||
'change input.currentValue': function(event, template) {
|
||||
// Get the DOM element representing the input box
|
||||
var input = $(event.currentTarget);
|
||||
|
||||
// Get the current value of the input
|
||||
var value = input.val();
|
||||
|
||||
// If the input is of type 'number', parse it as a Float
|
||||
if (input.attr('type') === 'number') {
|
||||
value = parseFloat(value);
|
||||
}
|
||||
|
||||
// Update the ReactiveVar with the user-specified value
|
||||
template.currentValue.set(value);
|
||||
},
|
||||
/**
|
||||
* Update the comparatorId ReactiveVar whenever the Comparators select box is changed
|
||||
*
|
||||
* @param event The Change event for the select box
|
||||
* @param template The current template context
|
||||
*/
|
||||
'change select.comparators': function(event, template) {
|
||||
// Get the current value of the select box
|
||||
var comparatorId = $(event.currentTarget).val();
|
||||
|
||||
// Update the ReactiveVar with the value of the Comparators select box
|
||||
template.comparatorId.set(comparatorId);
|
||||
}
|
||||
});
|
||||
@ -1,44 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
.ruleEntryDialog
|
||||
theme('background', '$uiGrayDarkest', 0.95)
|
||||
theme('border', '1px solid $uiBorderColor', 0.95)
|
||||
theme('color', '$textSecondaryColor')
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
z-index: 100
|
||||
width: 350px
|
||||
height: 230px
|
||||
margin: auto
|
||||
padding: 10px
|
||||
background-color: rgba(255,255,255,1)
|
||||
outline: none
|
||||
border-radius: 8px
|
||||
|
||||
.dialogContent
|
||||
text-align: center
|
||||
margin-bottom: 10px
|
||||
|
||||
.row
|
||||
margin: 15px 0
|
||||
|
||||
.btn
|
||||
text-decoration: none
|
||||
|
||||
#cancel
|
||||
float: left
|
||||
|
||||
#save
|
||||
float: right
|
||||
|
||||
input.currentValue
|
||||
width: 95%
|
||||
text-align: center
|
||||
padding: 4px
|
||||
color: black
|
||||
height: 30px
|
||||
border-radius: 2px
|
||||
border: 0
|
||||
@ -1,32 +0,0 @@
|
||||
<template name="ruleTable">
|
||||
<table class="ruleTable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Constraint</th>
|
||||
<th>Wt.</th>
|
||||
<th>Req.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ #each rules }}
|
||||
{{ #if constraint }}
|
||||
<tr>
|
||||
<td>
|
||||
<i class="fa fa-pencil editRule"></i>
|
||||
<i class="fa fa-trash deleteRule"></i>
|
||||
{{displayConstraint attribute constraint ../attributes}}
|
||||
{{ #unless rulePassed }}<small class="failWarning">Fail</small>{{ /unless }}
|
||||
</td>
|
||||
<td><input class="ruleWeight" type="number" value={{weight}} min="1" step="1" max="1000"/></td>
|
||||
<td><input class="ruleRequired" type="checkbox" {{valueIf required "checked" ''}}/></td>
|
||||
</tr>
|
||||
{{ /if }}
|
||||
{{ /each }}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="addRuleContainer">
|
||||
<span class="addRule">
|
||||
Add New Constraint <i class="fa fa-plus-circle"></i>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,130 +0,0 @@
|
||||
Template.ruleTable.helpers({
|
||||
/**
|
||||
* Retrieve validation data on each rule for the active viewport
|
||||
*
|
||||
* @returns {boolean} Whether or not the current rule passed for the active viewport
|
||||
*/
|
||||
rulePassed: function() {
|
||||
// Retrieve the latest match details given the active viewport index
|
||||
var viewportIndex = window.store.getState().viewports.activeViewport;
|
||||
var details = ProtocolEngine.matchDetails[viewportIndex];
|
||||
|
||||
// If no match was found, stop here
|
||||
if (!details || !details.bestMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the list of failed rules for this Viewport
|
||||
var failed = details.bestMatch.matchDetails.failed;
|
||||
|
||||
// Check if the current rule failed or not
|
||||
var rule = this;
|
||||
var hasPassed = true;
|
||||
failed.forEach(function(failedRuleData) {
|
||||
var failedRule = failedRuleData.rule;
|
||||
if (failedRule.id === rule.id) {
|
||||
hasPassed = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Return a boolean representing whether or not the rule passed
|
||||
return hasPassed;
|
||||
}
|
||||
});
|
||||
|
||||
Template.ruleTable.events({
|
||||
/**
|
||||
* Opens the Rule Entry dialog to allow the user to create a new rule
|
||||
* Specifies attributes and rule level for the Rule Entry dialog
|
||||
* based on the data given to this template.
|
||||
*/
|
||||
'click .addRule': function() {
|
||||
// Get the current template data
|
||||
var data = Template.currentData();
|
||||
|
||||
// Retrieve the rule attributes and level (e.g. study / series / instance)
|
||||
var attributes = data.attributes;
|
||||
var level = data.level;
|
||||
|
||||
// Open the Rule Entry Dialog with the attributes, level, and rule
|
||||
openRuleEntryDialog(attributes, level);
|
||||
},
|
||||
/**
|
||||
* Opens the Rule Entry dialog to allow the user to edit an existing
|
||||
* rule. Passes rule details to the dialog so its current properties
|
||||
* can be displayed.
|
||||
*
|
||||
* Specifies attributes and rule level for the Rule Entry dialog
|
||||
* based on the data given to this template.
|
||||
*/
|
||||
'click .editRule': function() {
|
||||
// Get the current template data
|
||||
var data = Template.currentData();
|
||||
|
||||
// Retrieve the rule attribtes and level (e.g. study / series / instance)
|
||||
var attributes = data.attributes;
|
||||
var level = data.level;
|
||||
|
||||
// Get the properties of the current rule
|
||||
var rule = this;
|
||||
|
||||
// Open the Rule Entry Dialog with the attributes, level, and rule
|
||||
openRuleEntryDialog(attributes, level, rule);
|
||||
},
|
||||
/**
|
||||
* Removes a rule from the current Viewport or Protocol depending on
|
||||
* the type of rule
|
||||
*/
|
||||
'click .deleteRule': function() {
|
||||
// Get the properties of the current rule
|
||||
var rule = this;
|
||||
|
||||
if (rule instanceof HP.ProtocolMatchingRule) {
|
||||
// If this Rule is evaluated at the protocol level,
|
||||
// remove it from the current Protocol
|
||||
ProtocolEngine.protocol.removeProtocolMatchingRule(rule);
|
||||
} else {
|
||||
// If this Rule is evaluated at the Viewport level,
|
||||
// remove it from the active viewport model
|
||||
var viewport = getActiveViewportModel();
|
||||
viewport.removeRule(rule);
|
||||
}
|
||||
|
||||
// Instruct the Protocol Engine to update the Layout Manager with new data
|
||||
var viewportIndex = window.store.getState().viewports.activeViewport;
|
||||
ProtocolEngine.updateViewports(viewportIndex);
|
||||
},
|
||||
/**
|
||||
* Updates a Rule's weight in response to user input
|
||||
*
|
||||
* @param event The input change event
|
||||
*/
|
||||
'change .ruleWeight': function(event) {
|
||||
// Get the properties of the current rule
|
||||
var rule = this;
|
||||
|
||||
// Update the value of the rule weight
|
||||
rule.weight = $(event.currentTarget).val();
|
||||
|
||||
// Instruct the Protocol Engine to update the Layout Manager with new data
|
||||
var viewportIndex = window.store.getState().viewports.activeViewport;
|
||||
ProtocolEngine.updateViewports(viewportIndex);
|
||||
},
|
||||
/**
|
||||
* Updates a Rule's 'required' property in response to user input
|
||||
*
|
||||
* @param event The input change event
|
||||
*/
|
||||
'change .ruleRequired': function(event) {
|
||||
// Get the properties of the current rule
|
||||
var rule = this;
|
||||
|
||||
// Update the value of the 'required' property
|
||||
rule.required = $(event.currentTarget).prop('checked');
|
||||
|
||||
// Instruct the Protocol Engine to update the Layout Manager with new data
|
||||
var viewportIndex = window.store.getState().viewports.activeViewport;
|
||||
ProtocolEngine.updateViewports(viewportIndex);
|
||||
}
|
||||
});
|
||||
@ -1,58 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
table.ruleTable
|
||||
thead
|
||||
tr
|
||||
th
|
||||
theme('color', '$defaultColor')
|
||||
text-align: center
|
||||
|
||||
th:first-child
|
||||
text-align: left
|
||||
|
||||
tbody
|
||||
tr
|
||||
td
|
||||
theme('color', '$defaultColor')
|
||||
text-align: center
|
||||
|
||||
.failWarning
|
||||
color: red
|
||||
|
||||
.editRule
|
||||
.deleteRule
|
||||
&:hover, &:active
|
||||
cursor: pointer
|
||||
transition(all 0.1s ease)
|
||||
|
||||
&:hover
|
||||
theme('color', '$hoverColor')
|
||||
|
||||
&:active
|
||||
theme('color', '$activeColor')
|
||||
|
||||
td:first-child
|
||||
text-align: left
|
||||
overflow-wrap: break-word
|
||||
word-wrap: break-word
|
||||
-ms-word-break: break-word
|
||||
word-break: break-word
|
||||
-ms-hyphens: auto
|
||||
-moz-hyphens: auto
|
||||
-webkit-hyphens: auto
|
||||
hyphens: auto
|
||||
|
||||
.addRuleContainer
|
||||
margin: 10px 0
|
||||
text-align: center
|
||||
theme('color', '$defaultColor')
|
||||
|
||||
.addRule
|
||||
cursor: pointer
|
||||
transition(all 0.1s ease)
|
||||
|
||||
&:hover
|
||||
theme('color', '$hoverColor')
|
||||
|
||||
&:active
|
||||
theme('color', '$activeColor')
|
||||
@ -1,25 +0,0 @@
|
||||
<template name="settingEntryDialog">
|
||||
<div class="settingEntryDialog dialog-animated">
|
||||
<div class="dialogHeader">
|
||||
<h5>Viewport Setting Editor</h5>
|
||||
</div>
|
||||
<div class="dialogContent">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<select class="settings" style="width: 95%;">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<select class="currentValue" style="width: 95%;">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialogFooter">
|
||||
<button id="cancel" class="btn btn-link" tabindex="1">Cancel</button>
|
||||
<button id="save" class="btn btn-primary" tabindex="0">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,246 +0,0 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import { Session } from 'meteor/session';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { Blaze } from 'meteor/blaze';
|
||||
import $ from 'jquery';
|
||||
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
|
||||
const keys = {
|
||||
ESC: 27
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the specified dialog element and return browser
|
||||
* focus to the active viewport.
|
||||
*
|
||||
* @param dialog The DOM element of the dialog to close
|
||||
*/
|
||||
function closeHandler(dialog) {
|
||||
// Hide the lesion dialog
|
||||
$(dialog).css('display', 'none');
|
||||
|
||||
// Remove the backdrop
|
||||
$('.removableBackdrop').remove();
|
||||
|
||||
// Restore the focus to the active viewport
|
||||
Viewerbase.setFocusToActiveViewport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays and updates the UI of the Setting Entry Dialog given an
|
||||
* optional setting to edit.
|
||||
*
|
||||
* @param settingObject
|
||||
*/
|
||||
openSettingEntryDialog = function(settingObject) {
|
||||
// Get the lesion location dialog
|
||||
var dialog = $('.settingEntryDialog');
|
||||
|
||||
// Store the Dialog DOM data, setting level and setting in the template data
|
||||
Template.settingEntryDialog.dialog = dialog;
|
||||
Template.settingEntryDialog.settingObject = settingObject;
|
||||
|
||||
// Initialize the Select2 search box for the attribute list
|
||||
var settings = Object.keys(HP.displaySettings);
|
||||
settings.concat(Object.keys(HP.CustomViewportSettings));
|
||||
|
||||
var displaySettingsOptions = Object.keys(HP.displaySettings).map(key => {
|
||||
return {
|
||||
id: key,
|
||||
text: HP.displaySettings[key].text
|
||||
};
|
||||
});
|
||||
|
||||
var customSettingsOptions = Object.keys(HP.CustomViewportSettings).map(key => {
|
||||
return {
|
||||
id: key,
|
||||
text: HP.CustomViewportSettings[key].text
|
||||
};
|
||||
});
|
||||
|
||||
var settingsOptions = displaySettingsOptions.concat(customSettingsOptions);
|
||||
|
||||
var settingSelect = dialog.find('.settings');
|
||||
settingSelect.html('').select2({
|
||||
data: settingsOptions,
|
||||
placeholder: 'Select a setting',
|
||||
allowClear: true
|
||||
});
|
||||
|
||||
var settingDetails = {
|
||||
options: []
|
||||
};
|
||||
|
||||
if (settingObject && HP.displaySettings[settingObject.id]) {
|
||||
settingDetails = HP.displaySettings[settingObject.id];
|
||||
} else if (settingObject && HP.CustomViewportSettings[settingObject.id]) {
|
||||
settingDetails = HP.CustomViewportSettings[settingObject.id];
|
||||
}
|
||||
|
||||
var valueSelect = dialog.find('.currentValue');
|
||||
valueSelect.html('').select2({
|
||||
data: settingDetails.options,
|
||||
placeholder: 'Select a value',
|
||||
allowClear: true
|
||||
});
|
||||
|
||||
// If a setting has been provided, set the value of the attribute Select2 input
|
||||
// to the attribute set in the setting.
|
||||
if (settingObject && settingObject.id) {
|
||||
settingSelect.val(settingObject.id);
|
||||
}
|
||||
|
||||
// Trigger('change') is used to update the Select2 choice in the UI
|
||||
// This is done after setting the value in case no setting was provided
|
||||
settingSelect.trigger('change');
|
||||
|
||||
// If a setting has been provided, display its current value
|
||||
if (settingObject && settingObject.value !== undefined) {
|
||||
valueSelect.val(settingObject.value).trigger('change');
|
||||
}
|
||||
|
||||
// Update the dialog's CSS so that it is visible on the page
|
||||
dialog.css('display', 'block');
|
||||
|
||||
// Show the backdrop
|
||||
Blaze.render(Template.removableBackdrop, document.body);
|
||||
|
||||
// Make sure the context menu is closed when the user clicks away
|
||||
$('.removableBackdrop').one('mousedown touchstart', function() {
|
||||
closeHandler(dialog);
|
||||
});
|
||||
};
|
||||
|
||||
Template.settingEntryDialog.onCreated(function() {
|
||||
// Define the ReactiveVars that will be used to link aspects of the UI
|
||||
var template = this;
|
||||
|
||||
// Note: currentValue's initial value must be a string so the template renders properly
|
||||
template.currentValue = new ReactiveVar('');
|
||||
template.setting = new ReactiveVar();
|
||||
});
|
||||
|
||||
Template.settingEntryDialog.onRendered(function() {
|
||||
const template = this;
|
||||
const dialog = template.$('.settingEntryDialog');
|
||||
dialog.draggable();
|
||||
});
|
||||
|
||||
Template.settingEntryDialog.events({
|
||||
/**
|
||||
* Save a setting that is being edited
|
||||
*
|
||||
* @param event the Click event
|
||||
* @param template The template context
|
||||
*/
|
||||
'click #save': function(event, template) {
|
||||
// Retrieve the input properties to the template
|
||||
var dialog = Template.settingEntryDialog.dialog;
|
||||
|
||||
// Retrieve the current values for the id and current value
|
||||
var setting = template.setting.get();
|
||||
var currentValue = template.currentValue.get();
|
||||
|
||||
// If currentValue input is undefined, prevent saving this setting
|
||||
if (currentValue === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var viewportSetting = {
|
||||
id: setting,
|
||||
value: currentValue
|
||||
};
|
||||
|
||||
// Obtain the active Viewport model from the Protocol and Stage
|
||||
var viewport = getActiveViewportModel();
|
||||
|
||||
// Remove any old rules if the ID has been changes
|
||||
var originalSettingObject = Template.settingEntryDialog.settingObject;
|
||||
if (originalSettingObject && originalSettingObject.id) {
|
||||
delete viewport.viewportSettings[originalSettingObject.id];
|
||||
}
|
||||
|
||||
// Update the active Viewport model' viewportSettings dictionary
|
||||
viewport.viewportSettings[viewportSetting.id] = viewportSetting.value;
|
||||
|
||||
// Instruct the Protocol Engine to update the Layout Manager with new data
|
||||
var viewportIndex = window.store.getState().viewports.activeViewport;
|
||||
ProtocolEngine.updateViewports(viewportIndex);
|
||||
|
||||
// Close the dialog
|
||||
closeHandler(dialog);
|
||||
},
|
||||
/**
|
||||
* Allow the user to click the Cancel button to close the dialog
|
||||
*/
|
||||
'click #cancel': function() {
|
||||
var dialog = Template.settingEntryDialog.dialog;
|
||||
closeHandler(dialog);
|
||||
},
|
||||
/**
|
||||
* Allow Esc keydown events to close the dialog
|
||||
*
|
||||
* @param event The Keydown event details
|
||||
* @returns {boolean} Return false to prevent bubbling of the event
|
||||
*/
|
||||
'keydown .settingEntryDialog': function(event) {
|
||||
var dialog = Template.settingEntryDialog.dialog;
|
||||
|
||||
// If Esc key is pressed, close the dialog
|
||||
if (event.which === keys.ESC) {
|
||||
closeHandler(dialog);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update the currentValue ReactiveVar if the user changes the attribute
|
||||
*
|
||||
* @param event The Change event for the select box
|
||||
* @param template The current template context
|
||||
*/
|
||||
'change select.settings': function(event, template) {
|
||||
// Obtain the user-specified attribute to test against
|
||||
var settingId = $(event.currentTarget).val();
|
||||
|
||||
// Store it in the ReactiveVar
|
||||
template.setting.set(settingId);
|
||||
|
||||
// Retrieve the current value from the attribute
|
||||
var settingDetails = {
|
||||
options: []
|
||||
};
|
||||
if (settingId && HP.displaySettings[settingId]) {
|
||||
settingDetails = HP.displaySettings[settingId];
|
||||
} else if (settingId && HP.CustomViewportSettings[settingId]) {
|
||||
settingDetails = HP.CustomViewportSettings[settingId];
|
||||
}
|
||||
|
||||
var dialog = Template.settingEntryDialog.dialog;
|
||||
var valueSelect = dialog.find('.currentValue');
|
||||
valueSelect.html('').select2({
|
||||
data: settingDetails.options,
|
||||
placeholder: 'Select a value',
|
||||
allowClear: true
|
||||
});
|
||||
|
||||
// Update the ReactiveVar with the user-specified value
|
||||
if (settingDetails && settingDetails.defaultValue) {
|
||||
template.currentValue.set(settingDetails.defaultValue);
|
||||
valueSelect.val(settingDetails.defaultValue).trigger('change');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update the currentValue ReactiveVar if the user changes the current value
|
||||
*
|
||||
* @param event The Change event for the input
|
||||
* @param template The current template context
|
||||
*/
|
||||
'change select.currentValue': function(event, template) {
|
||||
// Get the current value of the select box
|
||||
var value = $(event.currentTarget).val();
|
||||
|
||||
// Update the ReactiveVar with the user-specified value
|
||||
template.currentValue.set(value);
|
||||
}
|
||||
});
|
||||
@ -1,43 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
.settingEntryDialog
|
||||
theme('border', '1px solid $uiBorderColor', 0.95)
|
||||
theme('background', '$uiGrayDarkest', 0.95)
|
||||
theme('color', '$textSecondaryColor')
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
z-index: 100
|
||||
width: 300px
|
||||
height: 200px
|
||||
margin: auto
|
||||
padding: 10px
|
||||
outline: none
|
||||
border-radius: 8px
|
||||
|
||||
.dialogContent
|
||||
text-align: center
|
||||
margin-bottom: 10px
|
||||
|
||||
.row
|
||||
margin: 15px 0
|
||||
|
||||
.btn
|
||||
text-decoration: none
|
||||
|
||||
#cancel
|
||||
float: left
|
||||
|
||||
#save
|
||||
float: right
|
||||
|
||||
input.currentValue
|
||||
width: 95%
|
||||
text-align: center
|
||||
padding: 4px
|
||||
color: black
|
||||
height: 30px
|
||||
border-radius: 2px
|
||||
border: 0
|
||||
@ -1,31 +0,0 @@
|
||||
<template name="settingsTable">
|
||||
<table class="settingsTable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ #each objectToPairs settings }}
|
||||
{{#if value}}
|
||||
<tr>
|
||||
<td>
|
||||
<i class="fa fa-pencil editSetting"></i>
|
||||
<i class="fa fa-trash deleteSetting"></i>
|
||||
{{getSettingText}}
|
||||
</td>
|
||||
<td>
|
||||
{{prettyPrintStringify value}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
{{ /each }}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="addSettingContainer">
|
||||
<span class="addSetting">
|
||||
Add Display Setting <i class="fa fa-plus-circle"></i>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,57 +0,0 @@
|
||||
Template.settingsTable.events({
|
||||
/**
|
||||
* Opens the Setting Entry dialog to allow the user to create a new setting
|
||||
* Specifies attributes and setting level for the Setting Entry dialog
|
||||
* based on the data given to this template.
|
||||
*/
|
||||
'click .addSetting': function() {
|
||||
// Open the Setting Entry Dialog
|
||||
openSettingEntryDialog();
|
||||
},
|
||||
/**
|
||||
* Opens the Setting Entry dialog to allow the user to edit an existing
|
||||
* setting. Passes setting details to the dialog so its current properties
|
||||
* can be displayed.
|
||||
*
|
||||
* Specifies attributes for the Setting Entry dialog
|
||||
* based on the data given to this template.
|
||||
*/
|
||||
'click .editSetting': function() {
|
||||
// Get the properties of the current setting
|
||||
var setting = this;
|
||||
|
||||
// Open the Setting Entry Dialog with the setting
|
||||
openSettingEntryDialog(setting);
|
||||
},
|
||||
/**
|
||||
* Removes a setting from the current Viewport
|
||||
*/
|
||||
'click .deleteSetting': function() {
|
||||
// Get the properties of the current setting
|
||||
var setting = this;
|
||||
|
||||
// Retrieve the current viewport model
|
||||
var viewport = getActiveViewportModel();
|
||||
|
||||
// Remove the specified setting
|
||||
delete viewport.viewportSettings[setting.key];
|
||||
|
||||
// Instruct the Protocol Engine to update the Layout Manager with new data
|
||||
var viewportIndex = window.store.getState().viewports.activeViewport;
|
||||
ProtocolEngine.updateViewports(viewportIndex);
|
||||
}
|
||||
});
|
||||
|
||||
Template.settingsTable.helpers({
|
||||
getSettingText: function() {
|
||||
var setting = this;
|
||||
if (HP.CustomViewportSettings[setting.key]) {
|
||||
return HP.CustomViewportSettings[setting.key].text;
|
||||
} else if (HP.displaySettings[setting.key]) {
|
||||
return HP.displaySettings[setting.key].text;
|
||||
} else {
|
||||
return Blaze._globalHelpers['prettyPrintStringify'](setting.key);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
@ -1,48 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
table.settingsTable
|
||||
thead
|
||||
tr
|
||||
th
|
||||
theme('color', '$defaultColor')
|
||||
text-align: center
|
||||
|
||||
th:first-child
|
||||
text-align: left
|
||||
|
||||
tbody
|
||||
background: #3e3e3e
|
||||
|
||||
tr
|
||||
td
|
||||
theme('color', '$defaultColor')
|
||||
text-align: center
|
||||
|
||||
.editSetting
|
||||
.deleteSetting
|
||||
cursor: pointer
|
||||
transition(all 0.1s ease)
|
||||
|
||||
&:hover
|
||||
theme('color', '$hoverColor')
|
||||
|
||||
&:active
|
||||
theme('color', '$activeColor')
|
||||
|
||||
td:first-child
|
||||
text-align: left
|
||||
|
||||
.addSettingContainer
|
||||
margin: 10px 0
|
||||
text-align: center
|
||||
theme('color', '$defaultColor')
|
||||
|
||||
.addSetting
|
||||
cursor: pointer
|
||||
transition(all 0.1s ease)
|
||||
|
||||
&:hover
|
||||
theme('color', '$hoverColor')
|
||||
|
||||
&:active
|
||||
theme('color', '$activeColor')
|
||||
@ -1,33 +0,0 @@
|
||||
<template name="stageDetails">
|
||||
<div id="stageDetails" class="protocolEditorSection">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{{ #with activeViewport }}
|
||||
<div class="stageEditorSection">
|
||||
<h3>Viewport Settings</h3>
|
||||
<hr/>
|
||||
{{ >settingsTable settings=viewportSettings}}
|
||||
</div>
|
||||
|
||||
<div class="stageEditorSection">
|
||||
<h3>Study Matching Rules</h3>
|
||||
<hr/>
|
||||
{{ >ruleTable level="study" rules=studyMatchingRules attributes=studyAttributes}}
|
||||
</div>
|
||||
|
||||
<div class="stageEditorSection">
|
||||
<h3>Series Matching Rules</h3>
|
||||
<hr/>
|
||||
{{ >ruleTable level="series" rules=seriesMatchingRules attributes=seriesAttributes}}
|
||||
</div>
|
||||
|
||||
<div class="stageEditorSection">
|
||||
<h3>Image Matching Rules</h3>
|
||||
<hr/>
|
||||
{{ >ruleTable level="instance" rules=imageMatchingRules attributes=instanceAttributes}}
|
||||
</div>
|
||||
{{ /with }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,38 +0,0 @@
|
||||
getActiveViewportModel = function() {
|
||||
// If no ProtocolEngine has been defined yet, or there is
|
||||
// no currently displayed Protocol or Stage, stop here
|
||||
if (!ProtocolEngine ||
|
||||
!ProtocolEngine.protocol ||
|
||||
ProtocolEngine.stage === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the model of the currently displayed stage
|
||||
var stage = ProtocolEngine.getCurrentStageModel();
|
||||
|
||||
// Retrieve the index of the active viewport
|
||||
var activeViewport = window.store.getState().viewports.activeViewport;
|
||||
|
||||
// If the active viewport index is outside the bounds of the
|
||||
// number of Viewports defined for this Stage, stop here
|
||||
if (activeViewport >= stage.viewports.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return the Viewport model for this viewport index in the
|
||||
// current stage
|
||||
return stage.viewports[activeViewport];
|
||||
};
|
||||
|
||||
Template.stageDetails.helpers({
|
||||
/**
|
||||
* Retrieves the ViewportModel for the active viewport from the
|
||||
* currently displayed Protocol and display sequence Stage
|
||||
*
|
||||
* @returns {*} The Viewport model for the active viewport
|
||||
*/
|
||||
activeViewport: function() {
|
||||
|
||||
return getActiveViewportModel();
|
||||
}
|
||||
});
|
||||
@ -1,32 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
#stageDetails
|
||||
overflow-y: auto
|
||||
overflow-x: hidden
|
||||
padding-right: 16px
|
||||
padding-left: 20px
|
||||
margin-right: -16px
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
&::-webkit-scrollbar
|
||||
display: none
|
||||
|
||||
h3
|
||||
label
|
||||
theme('color', '$defaultColor')
|
||||
|
||||
label
|
||||
margin-right: 10px
|
||||
width: 25%
|
||||
|
||||
input
|
||||
theme('background-color', '$boxBackgroundColor')
|
||||
min-width: 50px
|
||||
width: 40%
|
||||
border: none
|
||||
theme('color', '$defaultColor')
|
||||
text-align: center
|
||||
|
||||
.stageEditorSection
|
||||
margin: 30px 0
|
||||
@ -1,30 +0,0 @@
|
||||
<template name="stageSortable">
|
||||
<div id="stageSortingContainer">
|
||||
<div class="moveStageButtons">
|
||||
<a class="moveStageUp noselect" disabled="{{ #unless isPreviousAvailable }}true{{ /unless }}">
|
||||
Move Stage Up <i class="fa fa-chevron-up"></i>
|
||||
</a>
|
||||
<a class="moveStageDown noselect" disabled="{{ #unless isNextAvailable }}true{{ /unless }}">
|
||||
Move Stage Down <i class="fa fa-chevron-down"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div id="stageSortable" class="sortable">
|
||||
{{#each stages}}
|
||||
<div class="sortable-item" data-id={{id}}>
|
||||
<span>
|
||||
<div class="sortable-handle pull-left"></div>
|
||||
<i class="deleteStage fa fa-trash fa-lg pull-right"></i>
|
||||
<span class="{{#if isActiveStage}}active{{/if}}">
|
||||
{{ stageLabel }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<div class="addStage">
|
||||
<span id="addStage">
|
||||
Create New Stage <i class="fa fa-plus-circle"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,243 +0,0 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import { Session } from 'meteor/session';
|
||||
|
||||
import { OHIF } from 'ohif-core';
|
||||
import 'meteor/ohif:viewerbase';
|
||||
|
||||
/**
|
||||
* Add an array index swapping function so we can swap stages more easily
|
||||
*/
|
||||
move = function(array, oldIndex, newIndex) {
|
||||
var value = array[oldIndex];
|
||||
|
||||
newIndex = Math.max(0, newIndex);
|
||||
newIndex = Math.min(array.length, newIndex);
|
||||
|
||||
array.splice(oldIndex, 1);
|
||||
array.splice(newIndex, 0, value);
|
||||
return array;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to obtain the current index of a stage in the
|
||||
* current protocol
|
||||
*
|
||||
* @param protocol The Hanging Protocol to search within
|
||||
* @param id The id of the current stage to search for
|
||||
* @returns {number} The index of the specified stage within the Protocol,
|
||||
* or undefined if it is not present.
|
||||
*/
|
||||
function getStageIndex(protocol, id) {
|
||||
var stageIndex;
|
||||
if (!protocol || !protocol.stages) {
|
||||
return;
|
||||
}
|
||||
|
||||
protocol.stages.forEach(function(stage, index) {
|
||||
if (stage.id === id) {
|
||||
stageIndex = index;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return stageIndex;
|
||||
}
|
||||
|
||||
Template.stageSortable.helpers({
|
||||
/**
|
||||
* Checks a specified stage to see if it is currently being displayed
|
||||
*
|
||||
* @returns {boolean} Whether or not the stage is currently being displayed
|
||||
*/
|
||||
isActiveStage: function() {
|
||||
// Rerun this function every time the layout manager has been updated
|
||||
|
||||
// If no Protocol Engine has been defined yet, stop here to prevent errors
|
||||
if (!ProtocolEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStage = ProtocolEngine.getCurrentStageModel();
|
||||
if (!currentStage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return a boolean representing if the active stage and the specified stage index are equal
|
||||
return (this.id === currentStage.id);
|
||||
},
|
||||
/**
|
||||
* Retrieves the index of the stage at the point it was last saved
|
||||
*
|
||||
* @returns {number|*}
|
||||
*/
|
||||
stageLabel: function() {
|
||||
var stage = this;
|
||||
|
||||
// If no Protocol Engine has been defined yet, stop here to prevent errors
|
||||
if (!ProtocolEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the last saved copy of the current protocol
|
||||
var lastSavedCopy = HP.ProtocolStore.getProtocol(ProtocolEngine.protocol.id);
|
||||
|
||||
// Try to find the index of this stage in the previously saved copy
|
||||
var 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
|
||||
// the label. Also include the time since it was created.
|
||||
if (stageIndex === undefined) {
|
||||
// Reactively update this helper every minute
|
||||
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;
|
||||
|
||||
// Use Moment.js to format the createdDate of this stage relative to the
|
||||
// current time
|
||||
var dateCreatedFromNow = moment(stage.createdDate).fromNow();
|
||||
|
||||
// Return the label for the new stage,
|
||||
// e.g. "New Stage 1 (created a few seconds ago)"
|
||||
return 'New Stage ' + newStageNumber + ' (created ' + dateCreatedFromNow + ')';
|
||||
}
|
||||
|
||||
// If the stage is not new, label it by the index it held in the stages array
|
||||
// at the previous saved point
|
||||
return 'Stage ' + ++stageIndex;
|
||||
},
|
||||
/**
|
||||
* Check if a later stage exists for the user to switch to
|
||||
*
|
||||
* @returns {boolean} Whether or not a later stage exists
|
||||
*/
|
||||
isNextAvailable: function() {
|
||||
// If no ProtocolEngine has been defined yet, stop here
|
||||
if (!ProtocolEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return whether or not the current stage is the last stage
|
||||
return ProtocolEngine.stage < ProtocolEngine.getNumProtocolStages() - 1;
|
||||
},
|
||||
/**
|
||||
* Check if an earlier stage exists for the user to switch to
|
||||
*
|
||||
* @returns {boolean} Whether or not an earlier stage exists
|
||||
*/
|
||||
isPreviousAvailable: function() {
|
||||
// Run this helper whenever the ProtocolEngine / LayoutManager has changed
|
||||
|
||||
// If no ProtocolEngine has been defined yet, stop here
|
||||
if (!ProtocolEngine) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return whether or not the current stage is the first stage
|
||||
return ProtocolEngine.stage > 0;
|
||||
}
|
||||
});
|
||||
|
||||
Template.stageSortable.events({
|
||||
/**
|
||||
* Displays a stage when its title is clicked
|
||||
*/
|
||||
'click .sortable-item span': function() {
|
||||
// Retrieve the index of this stage in the display set sequences
|
||||
var stageIndex = getStageIndex(ProtocolEngine.protocol, this.id);
|
||||
|
||||
// Display the selected stage
|
||||
ProtocolEngine.setCurrentProtocolStage(stageIndex - ProtocolEngine.stage);
|
||||
},
|
||||
/**
|
||||
* Creates a new stage and adds it to the currently loaded Protocol at
|
||||
* the end of the display set sequence
|
||||
*/
|
||||
'click #addStage': function() {
|
||||
// Retrieve the model describing the current stage
|
||||
var stage = ProtocolEngine.getCurrentStageModel();
|
||||
|
||||
// Clone this stage to create a new stage
|
||||
var newStage = stage.createClone();
|
||||
|
||||
// Remove the stage's name if it has one
|
||||
delete newStage.name;
|
||||
|
||||
// Append this new stage to the end of the display set sequence
|
||||
ProtocolEngine.protocol.stages.push(newStage);
|
||||
|
||||
// Append the new stage the list of new stage IDs, so we can label it properly
|
||||
ProtocolEngine.newStageIds.push(newStage.id);
|
||||
|
||||
// Switch to the next stage in the display set sequence
|
||||
ProtocolEngine.setCurrentProtocolStage(1);
|
||||
},
|
||||
/**
|
||||
* Deletes a stage from the currently loaded Protocol by removing it from
|
||||
* 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() {
|
||||
// If this is the only stage in the Protocol, stop here
|
||||
if (ProtocolEngine.protocol.stages.length === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stageId = this.id;
|
||||
|
||||
var options = {
|
||||
title: 'Remove Protocol Stage',
|
||||
text: 'Are you sure you would like to remove this Protocol Stage? This cannot be reversed.'
|
||||
};
|
||||
|
||||
OHIF.viewerbase.dialogUtils.showConfirmDialog(function() {
|
||||
// Retrieve the index of this stage in the display set sequences
|
||||
var stageIndex = getStageIndex(ProtocolEngine.protocol, stageId);
|
||||
|
||||
// Remove it from the display set sequence
|
||||
ProtocolEngine.protocol.stages.splice(stageIndex, 1);
|
||||
|
||||
// If we have removed the currently active stage, switch to the one before it
|
||||
if (ProtocolEngine.stage === stageIndex) {
|
||||
// Display the previous stage
|
||||
ProtocolEngine.setCurrentProtocolStage(-1);
|
||||
}
|
||||
}, options);
|
||||
},
|
||||
|
||||
'click .moveStageUp': function() {
|
||||
// Get the old and new indices following a 'sort' event
|
||||
var oldIndex = ProtocolEngine.stage;
|
||||
var newIndex = Math.max(ProtocolEngine.stage - 1, 0);
|
||||
|
||||
if (oldIndex === newIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap the stages in the current Protocol's display set sequence
|
||||
// using our addition to the Array prototype
|
||||
ProtocolEngine.protocol.stages = move(ProtocolEngine.protocol.stages, oldIndex, newIndex);
|
||||
|
||||
// If the currently displayed stage was reordered into a new position,
|
||||
// update the value for the stage index in the displayed Protocol
|
||||
ProtocolEngine.stage = newIndex;
|
||||
},
|
||||
'click .moveStageDown': function() {
|
||||
// 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);
|
||||
|
||||
if (oldIndex === newIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap the stages in the current Protocol's display set sequence
|
||||
// using our addition to the Array prototype
|
||||
ProtocolEngine.protocol.stages = move(ProtocolEngine.protocol.stages.move, oldIndex, newIndex);
|
||||
|
||||
// If the currently displayed stage was reordered into a new position,
|
||||
// update the value for the stage index in the displayed Protocol
|
||||
ProtocolEngine.stage = newIndex;
|
||||
}
|
||||
});
|
||||
@ -1,81 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
#stageSortingContainer
|
||||
padding: 0 20px
|
||||
|
||||
#stageSortable
|
||||
.sortable-item
|
||||
padding: 3px
|
||||
|
||||
span
|
||||
theme('color', '$defaultColor')
|
||||
cursor: pointer
|
||||
|
||||
&:hover
|
||||
theme('color', '$hoverColor')
|
||||
|
||||
&.active
|
||||
theme('color', '$activeColor')
|
||||
|
||||
.sortable-handle
|
||||
cursor: move
|
||||
cursor: -webkit-grabbing
|
||||
width: 15px
|
||||
height: 15px
|
||||
margin: 0 5px
|
||||
filter: invert(100%)
|
||||
-webkit-filter: invert(100%)
|
||||
background-image: unquote("url(/packages/hangingprotocols/assets/dots.svg)")
|
||||
|
||||
.sortable.target
|
||||
flex: 1 1 auto
|
||||
margin-left: 1em
|
||||
|
||||
.sortable-ghost
|
||||
opacity: 0.6
|
||||
|
||||
.deleteStage
|
||||
cursor: pointer
|
||||
theme('color', '$defaultColor')
|
||||
transition(all 0.1s ease)
|
||||
|
||||
&:hover
|
||||
theme('color', '$hoverColor')
|
||||
|
||||
&:active
|
||||
theme('color', '$activeColor')
|
||||
|
||||
.addStage
|
||||
margin: 10px 0
|
||||
text-align: center
|
||||
theme('color', '$defaultColor')
|
||||
|
||||
#addStage
|
||||
transition(all 0.1s ease)
|
||||
|
||||
&:hover
|
||||
theme('color', '$hoverColor')
|
||||
|
||||
&:active
|
||||
theme('color', '$activeColor')
|
||||
|
||||
.moveStageButtons
|
||||
margin: 10px 0
|
||||
text-align: center
|
||||
|
||||
a
|
||||
theme('color', '$defaultColor')
|
||||
cursor: pointer
|
||||
text-decoration: none
|
||||
transition(all 0.1s ease)
|
||||
|
||||
&:hover
|
||||
theme('color', '$hoverColor')
|
||||
|
||||
&:active
|
||||
theme('color', '$activeColor')
|
||||
|
||||
&[disabled="true"]
|
||||
opacity: 0.7
|
||||
cursor: disabled
|
||||
pointer-events: none
|
||||
@ -1,23 +0,0 @@
|
||||
<template name="textEntryDialog">
|
||||
<div class="textEntryDialog dialog-animated">
|
||||
<div class="dialogHeader">
|
||||
<h4 class="title">{{title}}</h4>
|
||||
</div>
|
||||
<div class="dialogContent">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<h5 class="instructions">{{instructions}}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<input class='currentValue' type="text" value={{currentValue}}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialogFooter">
|
||||
<button class="cancel btn btn-link" tabindex="1">Cancel</button>
|
||||
<button class="save btn btn-primary" tabindex="0">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,144 +0,0 @@
|
||||
import { Blaze } from 'meteor/blaze';
|
||||
import $ from 'jquery';
|
||||
import { Template } from 'meteor/templating';
|
||||
|
||||
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||
|
||||
const keys = {
|
||||
ESC: 27,
|
||||
ENTER: 13
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the specified dialog element and returns the browser
|
||||
* focus to the active viewport.
|
||||
*
|
||||
* @param dialog The DOM element of the dialog to close
|
||||
*/
|
||||
function closeHandler(dialog) {
|
||||
// Hide the lesion dialog
|
||||
$(dialog).css('display', 'none');
|
||||
|
||||
// Remove the backdrop
|
||||
$('.removableBackdrop').remove();
|
||||
|
||||
// Restore the focus to the active viewport
|
||||
Viewerbase.setFocusToActiveViewport();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays and updates the UI of the Text Entry Dialog given a new title,
|
||||
* instructions, and doneCallback
|
||||
*
|
||||
* @param title Title of the dialog box
|
||||
* @param instructions Instructions to display to the user
|
||||
* @param doneCallback Function to execute when the dialog has been closed
|
||||
*/
|
||||
openTextEntryDialog = function(title, instructions, currentValue, doneCallback) {
|
||||
// Get the lesion location dialog
|
||||
var dialog = $('.textEntryDialog');
|
||||
|
||||
// Clear any input that is still on the page
|
||||
var currentValueInput = dialog.find('input.currentValue');
|
||||
currentValueInput.val(currentValue);
|
||||
|
||||
// Store the Dialog DOM data, rule level and rule in the template data
|
||||
Template.textEntryDialog.dialog = dialog;
|
||||
Template.textEntryDialog.title = title;
|
||||
Template.textEntryDialog.instructions = instructions;
|
||||
Template.textEntryDialog.doneCallback = doneCallback;
|
||||
|
||||
dialog.find('.title').html(title);
|
||||
dialog.find('.instructions').html(instructions);
|
||||
|
||||
// Update the dialog's CSS so that it is visible on the page
|
||||
dialog.css('display', 'block');
|
||||
|
||||
// Show the backdrop
|
||||
UI.render(Template.removableBackdrop, document.body);
|
||||
|
||||
// Make sure the context menu is closed when the user clicks away
|
||||
$('.removableBackdrop').one('mousedown touchstart', function() {
|
||||
closeHandler(dialog);
|
||||
});
|
||||
};
|
||||
|
||||
Template.textEntryDialog.onRendered(() => {
|
||||
const instance = Template.instance();
|
||||
const dialog = instance.$('.settingEntryDialog');
|
||||
dialog.draggable();
|
||||
});
|
||||
|
||||
Template.textEntryDialog.events({
|
||||
/**
|
||||
* Save the user-specified text
|
||||
*
|
||||
*/
|
||||
'click .save': function() {
|
||||
// Retrieve the input properties to the template
|
||||
var dialog = Template.textEntryDialog.dialog;
|
||||
var currentValue = dialog.find('input.currentValue').val();
|
||||
|
||||
// If currentValue input is undefined, prevent saving this rule
|
||||
if (currentValue === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var doneCallback = Template.textEntryDialog.doneCallback;
|
||||
if (doneCallback) {
|
||||
doneCallback(currentValue);
|
||||
}
|
||||
|
||||
// Close the dialog
|
||||
closeHandler(Template.textEntryDialog.dialog);
|
||||
},
|
||||
/**
|
||||
* Allow the user to click the Cancel button to close the dialog
|
||||
*/
|
||||
'click .cancel': function() {
|
||||
closeHandler(Template.textEntryDialog.dialog);
|
||||
},
|
||||
/**
|
||||
* Allow Esc keydown events to close the dialog
|
||||
*
|
||||
* @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;
|
||||
|
||||
// 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();
|
||||
|
||||
// If currentValue input is undefined, prevent saving this rule
|
||||
if (currentValue === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var doneCallback = Template.textEntryDialog.doneCallback;
|
||||
if (doneCallback) {
|
||||
doneCallback(currentValue);
|
||||
}
|
||||
|
||||
closeHandler(dialog);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update the currentValue ReactiveVar if the user changes the attribute value
|
||||
*
|
||||
* @param event The Change event for the input
|
||||
* @param template The current template context
|
||||
*/
|
||||
'change input.currentValue': function(event, template) {
|
||||
// Get the DOM element representing the input box
|
||||
var input = $(event.currentTarget);
|
||||
|
||||
// Update the template data with the current value
|
||||
Template.textEntryDialog.currentValue = input.val();
|
||||
}
|
||||
});
|
||||
@ -1,36 +0,0 @@
|
||||
@import "{ohif:viewerbase}/app"
|
||||
|
||||
.textEntryDialog
|
||||
theme('border', '1px solid $uiBorderColor', 0.95)
|
||||
theme('background', '$uiGrayDarkest', 0.95)
|
||||
theme('color', '$textSecondaryColor')
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
z-index: 100
|
||||
width: 300px
|
||||
height: 170px
|
||||
margin: auto
|
||||
padding: 10px
|
||||
outline: none
|
||||
border-radius: 8px
|
||||
|
||||
.dialogContent
|
||||
margin-bottom: 10px
|
||||
|
||||
.cancel
|
||||
float: left
|
||||
|
||||
.save
|
||||
float: right
|
||||
|
||||
input.currentValue
|
||||
width: 95%
|
||||
text-align: left
|
||||
padding: 4px
|
||||
color: black
|
||||
height: 30px
|
||||
border-radius: 2px
|
||||
border: 0
|
||||
@ -1,28 +0,0 @@
|
||||
// Define an empty object to store callbacks that are used to retrieve custom attributes
|
||||
// The simplest example for a custom attribute is the Timepoint type (i.e. baseline or follow-up)
|
||||
// used in the LesionTracker application.
|
||||
//
|
||||
// Timepoint type can be obtained given a studyId, and this is done through a custom callback.
|
||||
// Developers can define attributes (i.e. attributeId = timepointType) with a name ('Timepoint Type')
|
||||
// and a callback function that is used to calculate them.
|
||||
//
|
||||
// The input to the callback, which is called during viewport-image matching rule evaluation
|
||||
// is the set of attributes that contains the specified attribute. In our example, timepointType is
|
||||
// linked to the study attributes, and so the inputs to the callback is an object containing
|
||||
// the study attributes.
|
||||
HP.CustomAttributeRetrievalCallbacks = {};
|
||||
|
||||
/**
|
||||
* Adds a custom attribute to be used in the HangingProtocol UI and matching rules, including a
|
||||
* callback that will be used to calculate the attribute value.
|
||||
*
|
||||
* @param attributeId The ID used to refer to the attribute (e.g. 'timepointType')
|
||||
* @param attributeName The name of the attribute to be displayed (e.g. 'Timepoint Type')
|
||||
* @param callback The function used to calculate the attribute value from the other attributes at its level (e.g. study/series/image)
|
||||
*/
|
||||
HP.addCustomAttribute = (attributeId, attributeName, callback) => {
|
||||
HP.CustomAttributeRetrievalCallbacks[attributeId] = {
|
||||
name: attributeName,
|
||||
callback: callback
|
||||
};
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
// Define an empty object to store callbacks that are used to apply custom viewport settings
|
||||
// after a viewport is rendered.
|
||||
HP.CustomViewportSettings = {};
|
||||
|
||||
/**
|
||||
* Adds a custom setting that can be chosen in the HangingProtocol UI and applied to a Viewport
|
||||
*
|
||||
* @param settingId The ID used to refer to the setting (e.g. 'displayCADMarkers')
|
||||
* @param settingName The name of the setting to be displayed (e.g. 'Display CAD Markers')
|
||||
* @param options
|
||||
* @param callback A function to be run after a viewport is rendered with a series
|
||||
*/
|
||||
HP.addCustomViewportSetting = (settingId, settingName, options, callback) => {
|
||||
HP.CustomViewportSettings[settingId] = {
|
||||
id: settingId,
|
||||
text: settingName,
|
||||
options: options,
|
||||
callback: callback
|
||||
};
|
||||
};
|
||||
@ -1,102 +0,0 @@
|
||||
HP.attributeDefaults = {
|
||||
abstractPriorValue: 0
|
||||
};
|
||||
|
||||
HP.displaySettings = {
|
||||
invert: {
|
||||
id: 'invert',
|
||||
text: 'Show Grayscale Inverted',
|
||||
defaultValue: 'NO',
|
||||
options: ['YES', 'NO']
|
||||
}
|
||||
};
|
||||
|
||||
// @TODO Fix abstractPriorValue comparison
|
||||
HP.studyAttributes = [{
|
||||
id: 'x00100020',
|
||||
text: '(x00100020) Patient ID'
|
||||
}, {
|
||||
id: 'x0020000d',
|
||||
text: '(x0020000d) Study Instance UID'
|
||||
}, {
|
||||
id: 'x00080020',
|
||||
text: '(x00080020) Study Date'
|
||||
}, {
|
||||
id: 'x00080030',
|
||||
text: '(x00080030) Study Time'
|
||||
}, {
|
||||
id: 'x00081030',
|
||||
text: '(x00081030) Study Description'
|
||||
}, {
|
||||
id: 'abstractPriorValue',
|
||||
text: 'Abstract Prior Value'
|
||||
}];
|
||||
|
||||
HP.protocolAttributes = [{
|
||||
id: 'x00100020',
|
||||
text: '(x00100020) Patient ID'
|
||||
}, {
|
||||
id: 'x0020000d',
|
||||
text: '(x0020000d) Study Instance UID'
|
||||
}, {
|
||||
id: 'x00080020',
|
||||
text: '(x00080020) Study Date'
|
||||
}, {
|
||||
id: 'x00080030',
|
||||
text: '(x00080030) Study Time'
|
||||
}, {
|
||||
id: 'x00081030',
|
||||
text: '(x00081030) Study Description'
|
||||
}, {
|
||||
id: 'anatomicRegion',
|
||||
text: 'Anatomic Region'
|
||||
}];
|
||||
|
||||
HP.seriesAttributes = [{
|
||||
id: 'x0020000e',
|
||||
text: '(x0020000e) Series Instance UID'
|
||||
}, {
|
||||
id: 'x00080060',
|
||||
text: '(x00080060) Modality'
|
||||
}, {
|
||||
id: 'x00200011',
|
||||
text: '(x00200011) Series Number'
|
||||
}, {
|
||||
id: 'x0008103e',
|
||||
text: '(x0008103e) Series Description'
|
||||
}, {
|
||||
id: 'numImages',
|
||||
text: 'Number of Images'
|
||||
}];
|
||||
|
||||
HP.instanceAttributes = [{
|
||||
id: 'x00080016',
|
||||
text: '(x00080016) SOP Class UID'
|
||||
}, {
|
||||
id: 'x00080018',
|
||||
text: '(x00080018) SOP Instance UID'
|
||||
}, {
|
||||
id: 'x00185101',
|
||||
text: '(x00185101) View Position'
|
||||
}, {
|
||||
id: 'x00200013',
|
||||
text: '(x00200013) Instance Number'
|
||||
}, {
|
||||
id: 'x00080008',
|
||||
text: '(x00080008) Image Type'
|
||||
}, {
|
||||
id: 'x00181063',
|
||||
text: '(x00181063) Frame Time'
|
||||
}, {
|
||||
id: 'x00200060',
|
||||
text: '(x00200060) Laterality'
|
||||
}, {
|
||||
id: 'x00541330',
|
||||
text: '(x00541330) Image Index'
|
||||
}, {
|
||||
id: 'x00280004',
|
||||
text: '(x00280004) Photometric Interpretation'
|
||||
}, {
|
||||
id: 'x00180050',
|
||||
text: '(x00180050) Slice Thickness'
|
||||
}];
|
||||
@ -1,22 +0,0 @@
|
||||
import { Blaze } from 'meteor/blaze';
|
||||
|
||||
|
||||
Blaze.registerHelper('viewportSettingsTypes', function() {
|
||||
return HP.viewportSettingsTypes;
|
||||
});
|
||||
|
||||
Blaze.registerHelper('toolSettingsTypes', function() {
|
||||
return HP.toolSettingsTypes;
|
||||
});
|
||||
|
||||
Blaze.registerHelper('studyAttributes', function() {
|
||||
return HP.studyAttributes;
|
||||
});
|
||||
|
||||
Blaze.registerHelper('seriesAttributes', function() {
|
||||
return HP.seriesAttributes;
|
||||
});
|
||||
|
||||
Blaze.registerHelper('instanceAttributes', function() {
|
||||
return HP.instanceAttributes;
|
||||
});
|
||||
@ -1,71 +0,0 @@
|
||||
import { Blaze } from 'meteor/blaze';
|
||||
|
||||
const attributeCache = Object.create(null);
|
||||
const REGEXP = /^\([x0-9a-f]+\)/;
|
||||
|
||||
const humanize = text => {
|
||||
let humanized = text.replace(/([A-Z])/g, ' $1'); // insert a space before all caps
|
||||
|
||||
humanized = humanized.replace(/^./, str => { // uppercase the first character
|
||||
return str.toUpperCase();
|
||||
});
|
||||
|
||||
return humanized;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text of an attribute for a given attribute
|
||||
* @param {String} attributeId The attribute ID
|
||||
* @param {Array} attributes Array of attributes objects with id and text properties
|
||||
* @return {String} If found return the attribute text or an empty string otherwise
|
||||
*/
|
||||
const getAttributeText = (attributeId, attributes) => {
|
||||
// If the attribute is already in the cache, return it
|
||||
if (attributeId in attributeCache) {
|
||||
return attributeCache[attributeId];
|
||||
}
|
||||
|
||||
// Find the attribute with given attributeId
|
||||
const attribute = attributes.find(attribute => attribute.id === attributeId);
|
||||
|
||||
let attributeText;
|
||||
|
||||
// If attribute was found get its text and save it on the cache
|
||||
if (attribute) {
|
||||
attributeText = attribute.text.replace(REGEXP, '');
|
||||
attributeCache[attributeId] = attributeText;
|
||||
}
|
||||
|
||||
return attributeText || '';
|
||||
};
|
||||
|
||||
Blaze.registerHelper('displayConstraint', (attributeId, constraint, attributes) => {
|
||||
if (!constraint || !attributeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const validatorType = Object.keys(constraint)[0];
|
||||
if (!validatorType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const validator = Object.keys(constraint[validatorType])[0];
|
||||
if (!validator) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = constraint[validatorType][validator];
|
||||
if (value === void 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let comparator = validator;
|
||||
if (validator === 'value') {
|
||||
comparator = validatorType;
|
||||
}
|
||||
|
||||
const attributeText = getAttributeText(attributeId, attributes);
|
||||
const constraintText = attributeText + ' ' + humanize(comparator).toLowerCase() + ' ' + value;
|
||||
|
||||
return constraintText;
|
||||
});
|
||||
@ -1,84 +0,0 @@
|
||||
const comparators = [{
|
||||
id: 'equals',
|
||||
name: '= (Equals)',
|
||||
validator: 'equals',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must equal this value.'
|
||||
}, {
|
||||
id: 'doesNotEqual',
|
||||
name: '!= (Does not equal)',
|
||||
validator: 'doesNotEqual',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must not equal this value.'
|
||||
}, {
|
||||
id: 'contains',
|
||||
name: 'Contains',
|
||||
validator: 'contains',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must contain this value.'
|
||||
}, {
|
||||
id: 'doesNotContain',
|
||||
name: 'Does not contain',
|
||||
validator: 'doesNotContain',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must not contain this value.'
|
||||
}, {
|
||||
id: 'startsWith',
|
||||
name: 'Starts with',
|
||||
validator: 'startsWith',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must start with this value.'
|
||||
}, {
|
||||
id: 'endsWith',
|
||||
name: 'Ends with',
|
||||
validator: 'endsWith',
|
||||
validatorOption: 'value',
|
||||
description: 'The attribute must end with this value.'
|
||||
}, {
|
||||
id: 'onlyInteger',
|
||||
name: 'Only Integers',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'onlyInteger',
|
||||
description: "Real numbers won't be allowed."
|
||||
}, {
|
||||
id: 'greaterThan',
|
||||
name: '> (Greater than)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'greaterThan',
|
||||
description: 'The attribute has to be greater than this value.'
|
||||
}, {
|
||||
id: 'greaterThanOrEqualTo',
|
||||
name: '>= (Greater than or equal to)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'greaterThanOrEqualTo',
|
||||
description: 'The attribute has to be at least this value.'
|
||||
}, {
|
||||
id: 'lessThanOrEqualTo',
|
||||
name: '<= (Less than or equal to)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'lessThanOrEqualTo',
|
||||
description: 'The attribute can be this value at the most.'
|
||||
}, {
|
||||
id: 'lessThan',
|
||||
name: '< (Less than)',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'lessThan',
|
||||
description: 'The attribute has to be less than this value.'
|
||||
}, {
|
||||
id: 'odd',
|
||||
name: 'Odd',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'odd',
|
||||
description: 'The attribute has to be odd.'
|
||||
}, {
|
||||
id: 'even',
|
||||
name: 'Even',
|
||||
validator: 'numericality',
|
||||
validatorOption: 'even',
|
||||
description: 'The attribute has to be even.'
|
||||
}];
|
||||
|
||||
// Immutable object
|
||||
Object.freeze(comparators);
|
||||
|
||||
export { comparators }
|
||||
@ -1,33 +0,0 @@
|
||||
import _ from 'underscore';
|
||||
|
||||
/**
|
||||
* Removes the first instance of an element from an array, if an equal value exists
|
||||
*
|
||||
* @param array
|
||||
* @param input
|
||||
*
|
||||
* @returns {boolean} Whether or not the element was found and removed
|
||||
*/
|
||||
const removeFromArray = (array, input) => {
|
||||
// If the array is empty, stop here
|
||||
if (!array ||
|
||||
!array.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
array.forEach((value, index) => {
|
||||
if (_.isEqual(value, input)) {
|
||||
indexToRemove = index;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (indexToRemove === void 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
array.splice(indexToRemove, 1);
|
||||
return true;
|
||||
};
|
||||
|
||||
export { removeFromArray };
|
||||
@ -1,8 +0,0 @@
|
||||
// Sorts an array by score
|
||||
const sortByScore = arr => {
|
||||
arr.sort((a, b) => {
|
||||
return b.score - a.score;
|
||||
});
|
||||
};
|
||||
|
||||
export { sortByScore };
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user