💥 _explosion noises_
This commit is contained in:
1 parent
ca98e69a66
commit
7e68259edb
888 files changed
-72169
No files matched your search
-12
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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');
|
|
||||||
Generated
-5797
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 |
Submodule Packages-react/ohif-core deleted from a728166da1.
Submodule Packages-react/react-cornerstone-viewport deleted from 1519ec03b2.
Submodule Packages-react/react-viewerbase deleted from a97200c7af.
Submodule Packages-react/react-vtkjs-viewport deleted from 09b6fb77ae.
@@ -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');
|
|
||||||
});
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
var CharLS=function(Module){Module=Module||{};var Module;if(!Module)Module=(typeof CharLS!=="undefined"?CharLS:null)||{};var moduleOverrides={};for(var key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var ENVIRONMENT_IS_WEB=typeof window==="object";var ENVIRONMENT_IS_WORKER=typeof importScripts==="function";var ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof require==="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER;var ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){if(!Module["print"])Module["print"]=function print(x){process["stdout"].write(x+"\n")};if(!Module["printErr"])Module["printErr"]=function printErr(x){process["stderr"].write(x+"\n")};var nodeFS=require("fs");var nodePath=require("path");Module["read"]=function read(filename,binary){filename=nodePath["normalize"](filename);var ret=nodeFS["readFileSync"](filename);if(!ret&&filename!=nodePath["resolve"](filename)){filename=path.join(__dirname,"..","src",filename);ret=nodeFS["readFileSync"](filename)}if(ret&&!binary)ret=ret.toString();return ret};Module["readBinary"]=function readBinary(filename){var ret=Module["read"](filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};Module["load"]=function load(f){globalEval(read(f))};if(!Module["thisProgram"]){if(process["argv"].length>1){Module["thisProgram"]=process["argv"][1].replace(/\\/g,"/")}else{Module["thisProgram"]="unknown-program"}}Module["arguments"]=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(!Module["print"])Module["print"]=print;if(typeof printErr!="undefined")Module["printErr"]=printErr;if(typeof read!="undefined"){Module["read"]=read}else{Module["read"]=function read(){throw"no read() available (jsc?)"}}Module["readBinary"]=function readBinary(f){if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}var data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){Module["arguments"]=scriptArgs}else if(typeof arguments!="undefined"){Module["arguments"]=arguments}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){Module["read"]=function read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(typeof arguments!="undefined"){Module["arguments"]=arguments}if(typeof console!=="undefined"){if(!Module["print"])Module["print"]=function print(x){console.log(x)};if(!Module["printErr"])Module["printErr"]=function printErr(x){console.log(x)}}else{var TRY_USE_DUMP=false;if(!Module["print"])Module["print"]=TRY_USE_DUMP&&typeof dump!=="undefined"?function(x){dump(x)}:function(x){}}if(ENVIRONMENT_IS_WORKER){Module["load"]=importScripts}if(typeof Module["setWindowTitle"]==="undefined"){Module["setWindowTitle"]=function(title){document.title=title}}}else{throw"Unknown runtime environment. Where are we?"}function globalEval(x){eval.call(null,x)}if(!Module["load"]&&Module["read"]){Module["load"]=function load(f){globalEval(Module["read"](f))}}if(!Module["print"]){Module["print"]=function(){}}if(!Module["printErr"]){Module["printErr"]=Module["print"]}if(!Module["arguments"]){Module["arguments"]=[]}if(!Module["thisProgram"]){Module["thisProgram"]="./this.program"}Module.print=Module["print"];Module.printErr=Module["printErr"];Module["preRun"]=[];Module["postRun"]=[];for(var key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}var Runtime={setTempRet0:function(value){tempRet0=value},getTempRet0:function(){return tempRet0},stackSave:function(){return STACKTOP},stackRestore:function(stackTop){STACKTOP=stackTop},getNativeTypeSize:function(type){switch(type){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(type[type.length-1]==="*"){return Runtime.QUANTUM_SIZE}else if(type[0]==="i"){var bits=parseInt(type.substr(1));assert(bits%8===0);return bits/8}else{return 0}}}},getNativeFieldSize:function(type){return Math.max(Runtime.getNativeTypeSize(type),Runtime.QUANTUM_SIZE)},STACK_ALIGN:16,prepVararg:function(ptr,type){if(type==="double"||type==="i64"){if(ptr&7){assert((ptr&7)===4);ptr+=4}}else{assert((ptr&3)===0)}return ptr},getAlignSize:function(type,size,vararg){if(!vararg&&(type=="i64"||type=="double"))return 8;if(!type)return Math.min(size,8);return Math.min(size||(type?Runtime.getNativeFieldSize(type):0),Runtime.QUANTUM_SIZE)},dynCall:function(sig,ptr,args){if(args&&args.length){if(!args.splice)args=Array.prototype.slice.call(args);args.splice(0,0,ptr);return Module["dynCall_"+sig].apply(null,args)}else{return Module["dynCall_"+sig].call(null,ptr)}},functionPointers:[],addFunction:function(func){for(var i=0;i<Runtime.funLine truncated
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
/*! cornerstone-wado-image-loader - 2.1.2 - 2018-06-05 | (c) 2016 Chris Hafey | https://github.com/cornerstonejs/cornerstoneWADOImageLoader */
|
|
||||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("cornerstoneWADOImageLoaderWebWorker",[],t):"object"==typeof exports?exports.cornerstoneWADOImageLoaderWebWorker=t():e.cornerstoneWADOImageLoaderWebWorker=t()}(this,function(){return function(e){var t={};function r(a){if(t[a])return t[a].exports;var n=t[a]={i:a,l:!1,exports:{}};return e[a].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,a){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:a})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var a=Object.create(null);if(r.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(a,n,function(t){return e[t]}.bind(null,n));return a},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=48)}([,function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e){for(var t=e[0],r=e[0],a=void 0,n=e.length,i=1;i<n;i++)a=e[i],t=Math.min(t,a),r=Math.max(r,a);return{min:t,max:r}}},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var a=void 0;function n(e,t){var r=function(e,t,r){var n=a._malloc(e.length);a.writeArrayToMemory(e,n);var i=a._malloc(4),o=a._malloc(4),l=a._malloc(4),f=a._malloc(4),s=a._malloc(4),u=(new Date).getTime();if(0!==a.ccall("jp2_decode","number",["number","number","number","number","number","number","number"],[n,e.length,i,o,l,f,s]))return console.log("[opj_decode] decoding failed!"),a._free(n),a._free(a.getValue(i,"*")),a._free(l),a._free(f),a._free(o),void a._free(s);var d=a.getValue(i,"*"),c={length:a.getValue(o,"i32"),sx:a.getValue(l,"i32"),sy:a.getValue(f,"i32"),nbChannels:a.getValue(s,"i32"),perf_timetodecode:void 0,pixelData:void 0},p=c.sx*c.sy*c.nbChannels,m=new Int32Array(a.HEAP32.buffer,d,p);if(1===t)if(Uint8Array.from)c.pixelData=Uint8Array.from(m);else{c.pixelData=new Uint8Array(p);for(var g=0;g<p;g++)c.pixelData[g]=m[g]}else if(r)if(Int16Array.from)c.pixelData=Int16Array.from(m);else{c.pixelData=new Int16Array(p);for(var y=0;y<p;y++)c.pixelData[y]=m[y]}else if(Uint16Array.from)c.pixelData=Uint16Array.from(m);else{c.pixelData=new Uint16Array(p);for(var b=0;b<p;b++)c.pixelData[b]=m[b]}var x=(new Date).getTime();return c.perf_timetodecode=x-u,a._free(n),a._free(i),a._free(d),a._free(o),a._free(l),a._free(f),a._free(s),c}(t,e.bitsAllocated<=8?1:2,1===e.pixelRepresentation);return e.columns=r.sx,e.rows=r.sy,e.pixelData=r.pixelData,r.nbChannels>1&&(e.photometricInterpretation="RGB"),e}function i(e){if(!e.usePDFJS&&"undefined"==typeof OpenJPEG)throw new Error("OpenJPEG decoder not loaded");if(!(a||(a=OpenJPEG())&&a._jp2_decode))throw new Error("OpenJPEG failed to initialize")}t.default=function(e,t,r){var a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};return i(r),a.usePDFJS||r.usePDFJS?function(e,t){var r=new JpxImage;r.parse(t);var a=r.tiles.length;if(1!==a)throw new Error("JPEG2000 decoder returned a tileCount of "+a+", when 1 is expected");return e.columns=r.width,e.rows=r.height,e.pixelData=r.tiles[0].items,e}(e,t):n(e,t)},t.initializeJPEG2000=i},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var a=void 0;function n(){if("undefined"==typeof CharLS)throw new Error("No JPEG-LS decoder loaded");if(!(a||(a=CharLS())&&a._jpegls_decode))throw new Error("JPEG-LS failed to initialize")}t.default=function(e,t){n();var r=function(e,t){var r=a._malloc(e.length);a.writeArrayToMemory(e,r);var n=a._malloc(4),i=a._malloc(4),o=a._malloc(4),l=a._malloc(4),f=a._malloc(4),s=a._malloc(4),u=a._malloc(4),d=a._malloc(4),c=a._malloc(4),p={result:a.ccall("jpegls_decode","number",["number","number","number","number","number","number","number","number","number","number","number"],[r,e.length,n,i,o,l,f,s,d,u,c]),width:a.getValue(o,"i32"),height:a.getValue(l,"i32"),bitsPerSample:a.getValue(f,"i32"),stride:a.getValue(s,"i32"),components:a.getValue(d,"i32"),allowedLossyError:a.getValue(u,"i32"),interleaveMode:a.getValue(c,"i32"),pixelData:void 0},m=a.getValue(n,"*");return p.bitsPerSample<=8?(p.pixelData=new Uint8Array(p.width*p.height*p.components),p.pixelData.set(new Uint8Array(a.HEAP8.buffer,m,p.pixelData.length))):t?(p.pixelData=new Int16Array(p.width*p.height*p.components),p.pixelData.set(new Int16Array(a.HEAP16.buffer,m,p.pixelData.length))):(p.pixelData=new Uint16Array(p.width*p.height*p.components),p.pixelData.set(new Uint16Array(a.HEAP16.buffer,m,p.pixelData.length))),a._free(r),a._free(m),a._free(n),a._free(i),a._free(o),a._free(l),a._free(f),a._free(s),a._free(d),a._free(c),p}(t,1=Line truncated
|
|
||||||
//# sourceMappingURL=cornerstoneWADOImageLoaderWebWorker.min.js.map
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["webpack://[name]/webpack/universalModuleDefinition","webpack://[name]/webpack/bootstrap","webpack://[name]/./shared/getMinMax.js","webpack://[name]/./shared/decoders/decodeJPEG2000.js","webpack://[name]/./shared/decoders/decodeJPEGLS.js","webpack://[name]/./version.js","webpack://[name]/./shared/calculateMinMax.js","webpack://[name]/./shared/decoders/decodeJPEGLossless.js","webpack://[name]/./shared/decoders/decodeJPEGBaseline.js","webpack://[name]/./shared/decoders/decodeRLE.js","webpack://[name]/./shared/decoders/decodeBigEndian.js","webpack://[name]/./shared/decoders/decodeLittleEndian.js","webpack://[name]/./shared/decodeImageFrame.js","webpack://[name]/./webWorker/decodeTask/decodeTask.js","webpack://[name]/./webWorker/webWorker.js","webpack://[name]/./webWorker/index.js"],"names":["root","factory","exports","module","define","amd","this","installedModules","__webpack_require__","moduleId","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","storedPixelData","min","max","storedPixel","numPixels","length","index","Math","openJPEG","decodeOpenJpeg2000","imageFrame","pixelData","image","data","bytesPerPixel","signed","dataPtr","_malloc","writeArrayToMemory","imagePtrPtr","imageSizePtr","imageSizeXPtr","imageSizeYPtr","imageSizeCompPtr","t0","Date","getTime","ccall","console","log","_free","getValue","imagePtr","sx","sy","nbChannels","perf_timetodecode","undefined","src32","Int32Array","HEAP32","buffer","Uint8Array","from","Int16Array","Uint16Array","t1","decodeOpenJPEG","bitsAllocated","pixelRepresentation","columns","rows","photometricInterpretation","initializeJPEG2000","decodeConfig","usePDFJS","OpenJPEG","Error","_jp2_decode","options","arguments","jpxImage","JpxImage","parse","tileCount","tiles","width","height","items","decodeJpx","charLS","initializeJPEGLS","CharLS","_jpegls_decode","isSigned","widthPtr","heightPtr","bitsPerSamplePtr","stridePtr","allowedLossyErrorPtr","componentsPtr","interleaveModePtr","result","bitsPerSample","stride","components","allowedLossyError","interleaveMode","set","HEAP8","HEAP16","jpegLSDecode","strict","minMax","_getMinMax2","default","mustAssign","isNumber","smallestPixelValue","largestPixelValue","warn","_getMinMax","numValue","jpeg","lossless","Decoder","byteOutput","decompressedData","decode","byteOffset","JpegImage","colorTransform","getData","getData16","planarConfiguration","frameData","frameSize","outFrame","ArrayBuffer","samplesPerPixel","header","DataView","Int8Array","out","outIndex","numSegments","getInt32","inIndex","maxIndex","endOfSegment","j","decode8Planar","decode8","highByte","decode16","arrayBuffer","offset","slice","val","Float32Array","transferSyntax","start","_decodeLittleEndian2","_decodeBigEndian2","_decodeRLE2","_decodeJPEGBaseline2","_decodeJPEGLossless2","_decodeJPEGLS2","_decodeJPEG2","shouldShift","shift","bitsStored","end","decodeTimeInMS","_decodeJPEG","_decodeJPEGLS","codecsLoaded","loadCodecs","config","self","importScripts","decodeTask","codecsPath","initializeCodecsOnStartup","taskType","handler","doneCallback","_decodeImageFrame2","_calculateMinMax2","initialize","loadCodecsOnStartup","registerTaskHandler","taskHandler","taskHandlers","initialized","taskConfiguration","onmessage","msg","sourcePath","transferList","postMessage","status","workerIndex","webWorkerTaskPaths","keys","forEach","_webWorker"],"mappings":";CAAA,SAAAA,EAAAC,GACA,iBAAAC,SAAA,iBAAAC,OACAA,OAAAD,QAAAD,IACA,mBAAAG,eAAAC,IACAD,OAAA,yCAAAH,GACA,iBAAAC,QACAA,QAAA,oCAAAD,IAEAD,EAAA,oCAAAC,IARA,CASCK,KAAA,WACD,mBCTA,IAAAC,KAGA,SAAAC,EAAAC,GAGA,GAAAF,EAAAE,GACA,OAAAF,EAAAE,GAAAP,QAGA,IAAAC,EAAAI,EAAAE,IACAC,EAAAD,EACAE,GAAA,EACAT,YAUA,OANAU,EAAAH,GAAAI,KAAAV,EAAAD,QAAAC,IAAAD,QAAAM,GAGAL,EAAAQ,GAAA,EAGAR,EAAAD,QA0DA,OArDAM,EAAAM,EAAAF,EAGAJ,EAAAO,EAAAR,EAGAC,EAAAQ,EAAA,SAAAd,EAAAe,EAAAC,GACAV,EAAAW,EAAAjB,EAAAe,IACAG,OAAAC,eAAAnB,EAAAe,GAA0CK,YAAA,EAAAC,IAAAL,KAK1CV,EAAAgB,EAAA,SAAAtB,GACA,oBAAAuB,eAAAC,aACAN,OAAAC,eAAAnB,EAAAuB,OAAAC,aAAwDC,MAAA,WAExDP,OAAAC,eAAAnB,EAAA,cAAiDyB,OAAA,KAQjDnB,EAAAoB,EAAA,SAAAD,EAAAE,GAEA,GADA,EAAAA,IAAAF,EAAAnB,EAAAmB,IACA,EAAAE,EAAA,OAAAF,EACA,KAAAE,GAAA,iBAAAF,QAAAG,WAAA,OAAAH,EACA,IAAAI,EAAAX,OAAAY,OAAA,MAGA,GAFAxB,EAAAgB,EAAAO,GACAX,OAAAC,eAAAU,EAAA,WAAyCT,YAAA,EAAAK,UACzC,EAAAE,GAAA,iBAAAF,EAAA,QAAAM,KAAAN,EAAAnB,EAAAQ,EAAAe,EAAAE,EAAA,SAAAA,GAAgH,OAAAN,EAAAM,IAAqBC,KAAA,KAAAD,IACrI,OAAAF,GAIAvB,EAAA2B,EAAA,SAAAhC,GACA,IAAAe,EAAAf,KAAA2B,WACA,WAA2B,OAAA3B,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAK,EAAAQ,EAAAE,EAAA,IAAAA,GACAA,GAIAV,EAAAW,EAAA,SAAAiB,EAAAC,GAAsD,OAAAjB,OAAAkB,UAAAC,eAAA1B,KAAAuB,EAAAC,IAGtD7B,EAAAgC,EAAA,GAIAhC,IAAAiC,EAAA,+FC5EA,SAAoBC,GASlB,IALA,IAAIC,EAAMD,EAAgB,GACtBE,EAAMF,EAAgB,GACtBG,SACEC,EAAYJ,EAAgBK,OAEzBC,EAAQ,EAAGA,EAAQF,EAAWE,IACrCH,EAAcH,EAAgBM,GAC9BL,EAAMM,KAAKN,IAAIA,EAAKE,GACpBD,EAAMK,KAALine truncated
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
-26
@@ -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>
|
|
||||||
-20
@@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
-14
@@ -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;
|
|
||||||
-19
@@ -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>
|
|
||||||
-43
@@ -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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
-19
@@ -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>
|
|
||||||
-43
@@ -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')
|
|
||||||
-47
@@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
-44
@@ -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')
|
|
||||||
-25
@@ -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>
|
|
||||||
-246
@@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
-43
@@ -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
|
|
||||||
-23
@@ -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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
-36
@@ -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 };
|
|
||||||
Loaded 100 of 888 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user