wip on react transition
This commit is contained in:
parent
5a4deedda8
commit
7425c2beb8
@ -51,3 +51,4 @@ cultofcoders:persistent-session
|
|||||||
shell-server@0.3.1
|
shell-server@0.3.1
|
||||||
underscore
|
underscore
|
||||||
meteortesting:mocha
|
meteortesting:mocha
|
||||||
|
react-template-helper
|
||||||
|
|||||||
@ -104,6 +104,7 @@ practicalmeteor:mocha-core@1.0.1
|
|||||||
promise@0.11.1
|
promise@0.11.1
|
||||||
raix:eventemitter@0.1.3
|
raix:eventemitter@0.1.3
|
||||||
random@1.1.0
|
random@1.1.0
|
||||||
|
react-template-helper@0.2.11
|
||||||
reactive-dict@1.2.0
|
reactive-dict@1.2.0
|
||||||
reactive-var@1.0.11
|
reactive-var@1.0.11
|
||||||
reload@1.2.0
|
reload@1.2.0
|
||||||
@ -123,6 +124,7 @@ templating@1.3.2
|
|||||||
templating-compiler@1.3.3
|
templating-compiler@1.3.3
|
||||||
templating-runtime@1.3.2
|
templating-runtime@1.3.2
|
||||||
templating-tools@1.1.2
|
templating-tools@1.1.2
|
||||||
|
tmeasday:check-npm-versions@0.3.2
|
||||||
tracker@1.2.0
|
tracker@1.2.0
|
||||||
ui@1.0.13
|
ui@1.0.13
|
||||||
underscore@1.0.10
|
underscore@1.0.10
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
{{> studyBrowser (clone this)}}
|
{{> studyBrowser (clone this)}}
|
||||||
</div>
|
</div>
|
||||||
<div class="mainContent {{#if leftSidebarOpen}}sidebar-left-open{{/if}} {{#if rightSidebarOpen}}sidebar-right-open{{/if}}">
|
<div class="mainContent {{#if leftSidebarOpen}}sidebar-left-open{{/if}} {{#if rightSidebarOpen}}sidebar-right-open{{/if}}">
|
||||||
{{> viewerMain (clone this)}}
|
{{> React component=ViewerMain studies=this.studies}}
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebarMenu sidebar-right {{#if rightSidebarOpen}}sidebar-open{{/if}}">
|
<div class="sidebarMenu sidebar-right {{#if rightSidebarOpen}}sidebar-open{{/if}}">
|
||||||
{{> measurementLightTable (clone this)}}
|
{{> measurementLightTable (clone this)}}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { Viewerbase } from 'meteor/ohif:viewerbase';
|
||||||
|
|
||||||
Template.flexboxLayout.events({
|
Template.flexboxLayout.events({
|
||||||
'transitionend .sidebarMenu'(event) {
|
'transitionend .sidebarMenu'(event) {
|
||||||
if (!event.target.classList.contains('sidebarMenu')) {
|
if (!event.target.classList.contains('sidebarMenu')) {
|
||||||
@ -15,5 +17,9 @@ Template.flexboxLayout.helpers({
|
|||||||
|
|
||||||
rightSidebarOpen() {
|
rightSidebarOpen() {
|
||||||
return Template.instance().data.state.get('rightSidebar');
|
return Template.instance().data.state.get('rightSidebar');
|
||||||
|
},
|
||||||
|
|
||||||
|
ViewerMain() {
|
||||||
|
return Viewerbase.components.viewer.ViewerMain;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
177
OHIFViewer/package-lock.json
generated
177
OHIFViewer/package-lock.json
generated
@ -155,6 +155,33 @@
|
|||||||
"path-is-absolute": "^1.0.0"
|
"path-is-absolute": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"history": {
|
||||||
|
"version": "4.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz",
|
||||||
|
"integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==",
|
||||||
|
"requires": {
|
||||||
|
"invariant": "^2.2.1",
|
||||||
|
"loose-envify": "^1.2.0",
|
||||||
|
"resolve-pathname": "^2.2.0",
|
||||||
|
"value-equal": "^0.4.0",
|
||||||
|
"warning": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"warning": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "2.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||||
|
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
|
||||||
|
},
|
||||||
"https-proxy-agent": {
|
"https-proxy-agent": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
|
||||||
@ -181,17 +208,48 @@
|
|||||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"invariant": {
|
||||||
|
"version": "2.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
|
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"isarray": {
|
"isarray": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"js-tokens": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
|
},
|
||||||
|
"lodash.clonedeep": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||||
|
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
|
||||||
|
},
|
||||||
|
"lodash.debounce": {
|
||||||
|
"version": "4.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
|
||||||
|
},
|
||||||
"loglevel": {
|
"loglevel": {
|
||||||
"version": "1.6.1",
|
"version": "1.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
|
||||||
"integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po="
|
"integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po="
|
||||||
},
|
},
|
||||||
|
"loose-envify": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
|
"requires": {
|
||||||
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"mime": {
|
"mime": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz",
|
||||||
@ -228,6 +286,11 @@
|
|||||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"object-assign": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
|
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||||
|
},
|
||||||
"once": {
|
"once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
@ -243,6 +306,21 @@
|
|||||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"path-to-regexp": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
|
||||||
|
"integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
|
||||||
|
"requires": {
|
||||||
|
"isarray": "0.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"isarray": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||||
|
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"pend": {
|
"pend": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||||
@ -261,6 +339,15 @@
|
|||||||
"integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=",
|
"integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"prop-types": {
|
||||||
|
"version": "15.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
|
||||||
|
"integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"object-assign": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"proxy-from-env": {
|
"proxy-from-env": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
|
||||||
@ -283,6 +370,55 @@
|
|||||||
"ws": "^5.1.1"
|
"ws": "^5.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react": {
|
||||||
|
"version": "16.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-16.6.3.tgz",
|
||||||
|
"integrity": "sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"scheduler": "^0.11.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"version": "16.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz",
|
||||||
|
"integrity": "sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"scheduler": "^0.11.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-router": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
|
||||||
|
"requires": {
|
||||||
|
"history": "^4.7.2",
|
||||||
|
"hoist-non-react-statics": "^2.5.0",
|
||||||
|
"invariant": "^2.2.4",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"path-to-regexp": "^1.7.0",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"warning": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-router-dom": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
|
||||||
|
"requires": {
|
||||||
|
"history": "^4.7.2",
|
||||||
|
"invariant": "^2.2.4",
|
||||||
|
"loose-envify": "^1.3.1",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"react-router": "^4.3.1",
|
||||||
|
"warning": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "2.3.6",
|
"version": "2.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||||
@ -298,11 +434,25 @@
|
|||||||
"util-deprecate": "~1.0.1"
|
"util-deprecate": "~1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redux": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.4.0",
|
||||||
|
"symbol-observable": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.11.1",
|
"version": "0.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||||
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
|
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
|
||||||
},
|
},
|
||||||
|
"resolve-pathname": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
|
||||||
|
},
|
||||||
"rimraf": {
|
"rimraf": {
|
||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||||
@ -318,6 +468,15 @@
|
|||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"scheduler": {
|
||||||
|
"version": "0.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.2.tgz",
|
||||||
|
"integrity": "sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"object-assign": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"string_decoder": {
|
"string_decoder": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
@ -327,6 +486,11 @@
|
|||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"symbol-observable": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
|
||||||
|
},
|
||||||
"typedarray": {
|
"typedarray": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
@ -339,6 +503,19 @@
|
|||||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"value-equal": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
|
||||||
|
},
|
||||||
|
"warning": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
|||||||
@ -5,7 +5,14 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.0.0-beta.51",
|
"@babel/runtime": "7.0.0-beta.51",
|
||||||
"loglevel": "^1.6.1"
|
"lodash.clonedeep": "^4.5.0",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"loglevel": "^1.6.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"react": "^16.6.3",
|
||||||
|
"react-dom": "^16.6.3",
|
||||||
|
"react-router-dom": "^4.3.1",
|
||||||
|
"redux": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"puppeteer": "^1.5.0"
|
"puppeteer": "^1.5.0"
|
||||||
|
|||||||
6
Packages/ohif-viewerbase/client/components/index.js
Normal file
6
Packages/ohif-viewerbase/client/components/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import viewer from './viewer/index.js';
|
||||||
|
|
||||||
|
export {
|
||||||
|
viewer
|
||||||
|
};
|
||||||
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
.CornerstoneViewport {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
@ -0,0 +1,645 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
//import * as cornerstone from 'cornerstone-core';
|
||||||
|
//import * as cornerstoneTools from 'cornerstone-tools';
|
||||||
|
|
||||||
|
import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone';
|
||||||
|
//import './lib/initCornerstone.js';
|
||||||
|
import ImageScrollbar from './ImageScrollbar.js';
|
||||||
|
import ViewportOverlay from './ViewportOverlay.js';
|
||||||
|
import ToolContextMenu from './ToolContextMenu.js';
|
||||||
|
import LoadingIndicator from './LoadingIndicator/LoadingIndicator.js';
|
||||||
|
import './CornerstoneViewport.css';
|
||||||
|
|
||||||
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
|
import debounce from 'lodash.debounce';
|
||||||
|
|
||||||
|
const EVENT_RESIZE = 'resize';
|
||||||
|
const loadIndicatorDelay = 45;
|
||||||
|
const { loadHandlerManager } = cornerstoneTools;
|
||||||
|
|
||||||
|
function setToolsPassive(tools) {
|
||||||
|
tools.forEach(tool => {
|
||||||
|
cornerstoneTools.setToolPassive(tool);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeTools(tools) {
|
||||||
|
Array.from(tools).forEach(tool => {
|
||||||
|
const apiTool = cornerstoneTools[`${tool.name}Tool`];
|
||||||
|
if (apiTool) {
|
||||||
|
cornerstoneTools.addTool(apiTool, tool.configuration);
|
||||||
|
} else {
|
||||||
|
throw new Error(`Tool not found: ${tool.name}Tool`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToIndex = cornerstoneTools.import('util/scrollToIndex');
|
||||||
|
|
||||||
|
class CornerstoneViewport extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const stack = props.viewportData.stack;
|
||||||
|
|
||||||
|
// TODO: Allow viewport as a prop
|
||||||
|
this.state = {
|
||||||
|
stack,
|
||||||
|
imageId: stack.imageIds[0],
|
||||||
|
viewportHeight: '100%',
|
||||||
|
isLoading: true,
|
||||||
|
imageScrollbarValue: 0,
|
||||||
|
numImagesLoaded: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.displayScrollbar = stack.imageIds.length > 1;
|
||||||
|
this.state.viewport = cornerstone.getDefaultViewport(null, undefined);
|
||||||
|
|
||||||
|
this.onImageRendered = this.onImageRendered.bind(this);
|
||||||
|
this.onNewImage = this.onNewImage.bind(this);
|
||||||
|
this.onWindowResize = this.onWindowResize.bind(this);
|
||||||
|
this.onImageLoaded = this.onImageLoaded.bind(this);
|
||||||
|
this.onStackScroll = this.onStackScroll.bind(this);
|
||||||
|
this.startLoadingHandler = this.startLoadingHandler.bind(this);
|
||||||
|
this.doneLoadingHandler = this.doneLoadingHandler.bind(this);
|
||||||
|
this.onMouseClick = this.onMouseClick.bind(this);
|
||||||
|
this.onTouchPress = this.onTouchPress.bind(this);
|
||||||
|
this.onMeasurementAddedOrRemoved = this.onMeasurementAddedOrRemoved.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
this.onCloseToolContextMenu = this.onCloseToolContextMenu.bind(this);
|
||||||
|
this.imageSliderOnInputCallback = this.imageSliderOnInputCallback.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
|
this.loadHandlerTimeout = 25;
|
||||||
|
loadHandlerManager.setStartLoadHandler(this.startLoadingHandler);
|
||||||
|
loadHandlerManager.setEndLoadHandler(this.doneLoadingHandler);
|
||||||
|
|
||||||
|
this.debouncedResize = _.debounce(() => {
|
||||||
|
cornerstone.resize(this.element, true);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
viewportHeight: `${this.element.clientHeight - 20}px`
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
this.slideTimeoutTime = 25;
|
||||||
|
this.slideTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const isLoading =
|
||||||
|
this.state.isLoading ||
|
||||||
|
this.state.numImagesLoaded / this.state.stack.imageIds.length < 0.1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ToolContextMenu
|
||||||
|
toolContextMenuData={this.state.toolContextMenuData}
|
||||||
|
onClose={this.onCloseToolContextMenu}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="CornerstoneViewport viewportElement"
|
||||||
|
onContextMenu={this.onContextMenu}
|
||||||
|
ref={input => {
|
||||||
|
this.element = input;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLoading ? <LoadingIndicator /> : ''}
|
||||||
|
<canvas className="cornerstone-canvas" />
|
||||||
|
<ViewportOverlay
|
||||||
|
stack={this.state.stack}
|
||||||
|
viewport={this.state.viewport}
|
||||||
|
imageId={this.state.imageId}
|
||||||
|
numImagesLoaded={this.state.numImagesLoaded}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{this.displayScrollbar && (
|
||||||
|
<ImageScrollbar
|
||||||
|
onInputCallback={this.imageSliderOnInputCallback}
|
||||||
|
max={this.state.stack.imageIds.length - 1}
|
||||||
|
value={this.state.imageScrollbarValue}
|
||||||
|
height={this.state.viewportHeight}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{/*this.state.bidirectionalAddLabelShow && (
|
||||||
|
<Labelling
|
||||||
|
measurementData={this.bidirectional.measurementData}
|
||||||
|
eventData={this.bidirectional.eventData}
|
||||||
|
labellingDoneCallback={this.bidirectional.labellingDoneCallback}
|
||||||
|
skipButton={this.bidirectional.skipButton}
|
||||||
|
editDescription={this.bidirectional.editDescription}
|
||||||
|
/>
|
||||||
|
)*/}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bidirectionalToolLabellingCallback = (
|
||||||
|
measurementData,
|
||||||
|
eventData,
|
||||||
|
doneCallback,
|
||||||
|
options = {}
|
||||||
|
) => {
|
||||||
|
const labellingDoneCallback = () => {
|
||||||
|
this.hideExtraButtons();
|
||||||
|
return doneCallback();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.bidirectional = {
|
||||||
|
measurementData,
|
||||||
|
eventData,
|
||||||
|
labellingDoneCallback,
|
||||||
|
skipButton: options.skipButton,
|
||||||
|
editDescription: options.editDescription
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
bidirectionalAddLabelShow: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onContextMenu(event) {
|
||||||
|
// Preventing the default behaviour for right-click is essential to
|
||||||
|
// allow right-click tools to work.
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowResize() {
|
||||||
|
this.debouncedResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
onImageRendered() {
|
||||||
|
const viewport = cornerstone.getViewport(this.element);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
viewport
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewImage() {
|
||||||
|
const image = cornerstone.getImage(this.element);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
imageId: image.imageId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const element = this.element;
|
||||||
|
|
||||||
|
// Enable the DOM Element for use with Cornerstone
|
||||||
|
cornerstone.enable(element);
|
||||||
|
|
||||||
|
cornerstone.events.addEventListener(
|
||||||
|
cornerstone.EVENTS.IMAGE_LOADED,
|
||||||
|
this.onImageLoaded
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load the first image in the stack
|
||||||
|
cornerstone.loadAndCacheImage(this.state.imageId).then(image => {
|
||||||
|
try {
|
||||||
|
cornerstone.getEnabledElement(element);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle cases where the user ends the session before the image is displayed.
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Soft Tissue preset for all images by default
|
||||||
|
const viewport = cornerstone.getDefaultViewportForImage(element, image);
|
||||||
|
viewport.voi = {
|
||||||
|
windowWidth: 400,
|
||||||
|
windowCenter: 40
|
||||||
|
};
|
||||||
|
|
||||||
|
// Display the first image
|
||||||
|
cornerstone.displayImage(element, image, viewport);
|
||||||
|
|
||||||
|
// Clear any previous tool state
|
||||||
|
cornerstoneTools.clearToolState(this.element, 'stack');
|
||||||
|
|
||||||
|
// Disable stack prefetch in case there are still queued requests
|
||||||
|
cornerstoneTools.stackPrefetch.disable(this.element);
|
||||||
|
|
||||||
|
// Add the stack tool state to the enabled element
|
||||||
|
const stack = this.state.stack;
|
||||||
|
cornerstoneTools.addStackStateManager(element, ['stack']);
|
||||||
|
cornerstoneTools.addToolState(element, 'stack', stack);
|
||||||
|
cornerstoneTools.stackPrefetch.enable(this.element);
|
||||||
|
|
||||||
|
const tools = [
|
||||||
|
{
|
||||||
|
name: 'Bidirectional',
|
||||||
|
configuration: {
|
||||||
|
getMeasurementLocationCallback: this
|
||||||
|
.bidirectionalToolLabellingCallback,
|
||||||
|
shadow: true,
|
||||||
|
drawHandlesOnHover: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ name: 'Wwwc' },
|
||||||
|
{
|
||||||
|
name: 'Zoom',
|
||||||
|
configuration: {
|
||||||
|
minScale: 0.3,
|
||||||
|
maxScale: 25,
|
||||||
|
preventZoomOutsideImage: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ name: 'Pan' },
|
||||||
|
{ name: 'StackScroll' },
|
||||||
|
{ name: 'PanMultiTouch' },
|
||||||
|
{ name: 'ZoomTouchPinch' },
|
||||||
|
{ name: 'StackScrollMouseWheel' },
|
||||||
|
{ name: 'StackScrollMultiTouch' }
|
||||||
|
];
|
||||||
|
|
||||||
|
initializeTools(tools);
|
||||||
|
|
||||||
|
this.setActiveTool(this.props.activeTool);
|
||||||
|
|
||||||
|
/* For touch devices, by default we activate:
|
||||||
|
- Pinch to zoom
|
||||||
|
- Two-finger Pan
|
||||||
|
- Three (or more) finger Stack Scroll
|
||||||
|
*/
|
||||||
|
cornerstoneTools.setToolActive('PanMultiTouch', {
|
||||||
|
mouseButtonMask: 0,
|
||||||
|
isTouchActive: true
|
||||||
|
});
|
||||||
|
cornerstoneTools.setToolActive('ZoomTouchPinch', {
|
||||||
|
mouseButtonMask: 0,
|
||||||
|
isTouchActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
cornerstoneTools.setToolActive('StackScrollMultiTouch', {
|
||||||
|
mouseButtonMask: 0,
|
||||||
|
isTouchActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
cornerstoneTools.stackPrefetch.setConfiguration({
|
||||||
|
maxImagesToPrefetch: Infinity,
|
||||||
|
preserveExistingPool: false,
|
||||||
|
maxSimultaneousRequests: 6
|
||||||
|
});
|
||||||
|
|
||||||
|
/* For mouse devices, by default we turn on:
|
||||||
|
- Stack scrolling by mouse wheel
|
||||||
|
- Stack scrolling by keyboard up / down arrow keys
|
||||||
|
- Pan with middle click
|
||||||
|
- Zoom with right click
|
||||||
|
*/
|
||||||
|
|
||||||
|
cornerstoneTools.setToolActive('StackScrollMouseWheel', {
|
||||||
|
mouseButtonMask: 0,
|
||||||
|
isTouchActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
element.addEventListener(
|
||||||
|
cornerstone.EVENTS.IMAGE_RENDERED,
|
||||||
|
this.onImageRendered
|
||||||
|
);
|
||||||
|
|
||||||
|
element.addEventListener(cornerstone.EVENTS.NEW_IMAGE, this.onNewImage);
|
||||||
|
|
||||||
|
element.addEventListener(
|
||||||
|
cornerstoneTools.EVENTS.STACK_SCROLL,
|
||||||
|
this.onStackScroll
|
||||||
|
);
|
||||||
|
|
||||||
|
element.addEventListener(
|
||||||
|
cornerstoneTools.EVENTS.MEASUREMENT_ADDED,
|
||||||
|
this.onMeasurementAddedOrRemoved
|
||||||
|
);
|
||||||
|
|
||||||
|
element.addEventListener(
|
||||||
|
cornerstoneTools.EVENTS.MEASUREMENT_REMOVED,
|
||||||
|
this.onMeasurementAddedOrRemoved
|
||||||
|
);
|
||||||
|
|
||||||
|
element.addEventListener(
|
||||||
|
cornerstoneTools.EVENTS.MOUSE_CLICK,
|
||||||
|
this.onMouseClick
|
||||||
|
);
|
||||||
|
|
||||||
|
element.addEventListener(
|
||||||
|
cornerstoneTools.EVENTS.TOUCH_PRESS,
|
||||||
|
this.onTouchPress
|
||||||
|
);
|
||||||
|
|
||||||
|
window.addEventListener(EVENT_RESIZE, this.onWindowResize);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
viewportHeight: `${this.element.clientHeight - 20}px`
|
||||||
|
});
|
||||||
|
|
||||||
|
this.doneLoadingHandler();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const element = this.element;
|
||||||
|
element.removeEventListener(
|
||||||
|
cornerstone.EVENTS.IMAGE_RENDERED,
|
||||||
|
this.onImageRendered
|
||||||
|
);
|
||||||
|
|
||||||
|
element.removeEventListener(cornerstone.EVENTS.NEW_IMAGE, this.onNewImage);
|
||||||
|
|
||||||
|
element.removeEventListener(
|
||||||
|
cornerstoneTools.EVENTS.STACK_SCROLL,
|
||||||
|
this.onStackScroll
|
||||||
|
);
|
||||||
|
|
||||||
|
element.removeEventListener(
|
||||||
|
cornerstoneTools.EVENTS.MEASUREMENT_ADDED,
|
||||||
|
this.onMeasurementAddedOrRemoved
|
||||||
|
);
|
||||||
|
|
||||||
|
element.removeEventListener(
|
||||||
|
cornerstoneTools.EVENTS.MEASUREMENT_REMOVED,
|
||||||
|
this.onMeasurementAddedOrRemoved
|
||||||
|
);
|
||||||
|
|
||||||
|
element.removeEventListener(
|
||||||
|
cornerstoneTools.EVENTS.MOUSE_CLICK,
|
||||||
|
this.onMouseClick
|
||||||
|
);
|
||||||
|
|
||||||
|
element.removeEventListener(
|
||||||
|
cornerstoneTools.EVENTS.TOUCH_PRESS,
|
||||||
|
this.onTouchPress
|
||||||
|
);
|
||||||
|
|
||||||
|
window.removeEventListener(EVENT_RESIZE, this.onWindowResize);
|
||||||
|
|
||||||
|
cornerstone.disable(element);
|
||||||
|
|
||||||
|
cornerstone.events.removeEventListener(
|
||||||
|
cornerstone.EVENTS.IMAGE_LOADED,
|
||||||
|
this.onImageLoaded
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
// TODO: Add a real object shallow comparison here?
|
||||||
|
if (
|
||||||
|
this.state.stack.imageIds[0] !== this.props.viewportData.stack.imageIds[0]
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
stack: this.props.viewportData.stack
|
||||||
|
});
|
||||||
|
|
||||||
|
const stackData = cornerstoneTools.getToolState(this.element, 'stack');
|
||||||
|
let currentStack = stackData && stackData.data[0];
|
||||||
|
|
||||||
|
if (!currentStack) {
|
||||||
|
currentStack = {
|
||||||
|
currentImageIdIndex: this.state.stack.currentImageIdIndex,
|
||||||
|
imageIds: this.state.stack.imageIds
|
||||||
|
};
|
||||||
|
|
||||||
|
cornerstoneTools.addStackStateManager(this.element, ['stack']);
|
||||||
|
cornerstoneTools.addToolState(this.element, 'stack', currentStack);
|
||||||
|
} else {
|
||||||
|
// TODO: we should make something like setToolState by an ID
|
||||||
|
currentStack.currentImageIdIndex = this.state.stack.currentImageIdIndex;
|
||||||
|
currentStack.imageIds = this.state.stack.imageIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageId = currentStack.imageIds[currentStack.currentImageIdIndex];
|
||||||
|
|
||||||
|
cornerstone.loadAndCacheImage(imageId).then(image => {
|
||||||
|
try {
|
||||||
|
cornerstone.getEnabledElement(this.element);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle cases where the user ends the session before the image is displayed.
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cornerstone.displayImage(this.element, image);
|
||||||
|
|
||||||
|
cornerstoneTools.stackPrefetch.disable(this.element);
|
||||||
|
cornerstoneTools.stackPrefetch.enable(this.element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.activeTool !== prevProps.activeTool) {
|
||||||
|
this.setActiveTool(this.props.activeTool);
|
||||||
|
|
||||||
|
// TODO: Why do we need to do this in v3?
|
||||||
|
cornerstoneTools.setToolActive('StackScrollMouseWheel', {
|
||||||
|
mouseButtonMask: 0,
|
||||||
|
isTouchActive: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.currentLesion !== prevProps.currentLesion) {
|
||||||
|
const currentToolData = this.props.toolData[this.props.currentLesion - 1];
|
||||||
|
if (currentToolData) {
|
||||||
|
const { imageId } = currentToolData;
|
||||||
|
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
||||||
|
const toolData = toolState[imageId][currentToolData.toolType].data;
|
||||||
|
|
||||||
|
toolData.forEach(data => {
|
||||||
|
if (data._id === currentToolData._id) {
|
||||||
|
data.active = true;
|
||||||
|
} else {
|
||||||
|
data.active = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cornerstoneTools.globalImageIdSpecificToolStateManager.restoreToolState(
|
||||||
|
toolState
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.state.imageId === imageId) {
|
||||||
|
cornerstone.setViewport(this.element, currentToolData.viewport);
|
||||||
|
cornerstone.updateImage(this.element);
|
||||||
|
} else {
|
||||||
|
cornerstone.loadAndCacheImage(imageId).then(image => {
|
||||||
|
try {
|
||||||
|
cornerstone.getEnabledElement(this.element);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle cases where the user ends the session before the image is displayed.
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cornerstone.displayImage(
|
||||||
|
this.element,
|
||||||
|
image,
|
||||||
|
currentToolData.viewport
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
imageId
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cornerstone.updateImage(this.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveTool = activeTool => {
|
||||||
|
const leftMouseTools = ['Bidirectional', 'Wwwc', 'StackScroll'];
|
||||||
|
|
||||||
|
setToolsPassive(leftMouseTools);
|
||||||
|
|
||||||
|
// pan is the default tool for middle mouse button
|
||||||
|
const isPanToolActive = activeTool === 'Pan';
|
||||||
|
const panOptions = {
|
||||||
|
mouseButtonMask: isPanToolActive ? [1, 4] : [4],
|
||||||
|
isTouchActive: isPanToolActive
|
||||||
|
};
|
||||||
|
cornerstoneTools.setToolActive('Pan', panOptions);
|
||||||
|
|
||||||
|
// zoom is the default tool for right mouse button
|
||||||
|
const isZoomToolActive = activeTool === 'Zoom';
|
||||||
|
const zoomOptions = {
|
||||||
|
mouseButtonMask: isZoomToolActive ? [1, 2] : [2],
|
||||||
|
isTouchActive: isZoomToolActive
|
||||||
|
};
|
||||||
|
cornerstoneTools.setToolActive('Zoom', zoomOptions);
|
||||||
|
|
||||||
|
cornerstoneTools.setToolActive(activeTool, {
|
||||||
|
mouseButtonMask: 1,
|
||||||
|
isTouchActive: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onStackScroll(event) {
|
||||||
|
const element = event.currentTarget;
|
||||||
|
const stackData = cornerstoneTools.getToolState(element, 'stack');
|
||||||
|
const stack = stackData.data[0];
|
||||||
|
|
||||||
|
this.hideExtraButtons();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
stack,
|
||||||
|
imageScrollbarValue: stack.currentImageIdIndex
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onImageLoaded(event) {
|
||||||
|
this.setState({
|
||||||
|
numImagesLoaded: this.state.numImagesLoaded + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startLoadingHandler() {
|
||||||
|
//console.log('startLoadingHandler');
|
||||||
|
clearTimeout(this.loadHandlerTimeout);
|
||||||
|
this.loadHandlerTimeout = setTimeout(() => {
|
||||||
|
this.setState({
|
||||||
|
isLoading: true
|
||||||
|
});
|
||||||
|
}, loadIndicatorDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
doneLoadingHandler() {
|
||||||
|
clearTimeout(this.loadHandlerTimeout);
|
||||||
|
this.setState({
|
||||||
|
isLoading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMeasurementAddedOrRemoved(event) {
|
||||||
|
console.log('onMeasurementAddedOrRemoved');
|
||||||
|
/*const { toolType, measurementData } = event.detail;
|
||||||
|
|
||||||
|
// TODO: Pass in as prop?
|
||||||
|
const toolsOfInterest = ['Bidirectional'];
|
||||||
|
|
||||||
|
this.hideExtraButtons();
|
||||||
|
|
||||||
|
if (toolsOfInterest.includes(toolType)) {
|
||||||
|
const image = cornerstone.getImage(this.element);
|
||||||
|
const viewport = cornerstone.getViewport(this.element);
|
||||||
|
|
||||||
|
const type = {
|
||||||
|
cornerstonetoolsmeasurementadded: 'added',
|
||||||
|
cornerstonetoolsmeasurementremoved: 'removed'
|
||||||
|
};
|
||||||
|
const action = type[event.type];
|
||||||
|
|
||||||
|
if (action === 'added') {
|
||||||
|
measurementData._id = guid();
|
||||||
|
measurementData.viewport = cloneDeep(viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.measurementsAddedOrRemoved(
|
||||||
|
action,
|
||||||
|
image.imageId,
|
||||||
|
toolType,
|
||||||
|
measurementData
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseClick(event) {
|
||||||
|
if (event.detail.event.which === 3) {
|
||||||
|
this.setState({
|
||||||
|
toolContextMenuData: {
|
||||||
|
eventData: event.detail,
|
||||||
|
isTouchEvent: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onTouchPress(event) {
|
||||||
|
this.setState({
|
||||||
|
toolContextMenuData: {
|
||||||
|
eventData: event.detail,
|
||||||
|
isTouchEvent: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseToolContextMenu() {
|
||||||
|
this.setState({
|
||||||
|
toolContextMenuData: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
imageSliderOnInputCallback(value) {
|
||||||
|
this.setState({
|
||||||
|
imageScrollbarValue: value
|
||||||
|
});
|
||||||
|
|
||||||
|
// Note that we throttle requests to prevent the
|
||||||
|
// user's ultrafast scrolling from firing requests too quickly.
|
||||||
|
//clearTimeout(this.slideTimeout);
|
||||||
|
//this.slideTimeout = setTimeout(() => {
|
||||||
|
scrollToIndex(this.element, value);
|
||||||
|
//}, this.slideTimeoutTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideExtraButtons = () => {
|
||||||
|
if (this.state.bidirectionalAddLabelShow === true) {
|
||||||
|
this.setState({
|
||||||
|
bidirectionalAddLabelShow: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
toolContextMenuData: null
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
CornerstoneViewport.propTypes = {
|
||||||
|
measurementsAddedOrRemoved: PropTypes.func.isRequired,
|
||||||
|
measurementsChanged: PropTypes.func.isRequired,
|
||||||
|
activeTool: PropTypes.string.isRequired,
|
||||||
|
viewportData: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CornerstoneViewport;
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
import CornerstoneViewport from '../CornerstoneViewport.js';
|
||||||
|
import './GridLayout.styl';
|
||||||
|
|
||||||
|
const TOP_CLASS = 'top';
|
||||||
|
const BOTTOM_CLASS = 'bottom';
|
||||||
|
const MIDDLE_CLASS = 'middle';
|
||||||
|
|
||||||
|
class GridLayout extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.getClass = this.getClass.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get class for each viewport, so each app
|
||||||
|
// using ohif-viewerbase can style on their own
|
||||||
|
getClass(index) {
|
||||||
|
const { rows, columns } = this.props;
|
||||||
|
|
||||||
|
if (rows === 1) {
|
||||||
|
return `${TOP_CLASS} ${BOTTOM_CLASS}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actualRow = Math.floor(index / columns);
|
||||||
|
|
||||||
|
if ( actualRow === 0 ) {
|
||||||
|
return TOP_CLASS;
|
||||||
|
}
|
||||||
|
if ( actualRow + 1 === rows ) {
|
||||||
|
return BOTTOM_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MIDDLE_CLASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveClass(index) {
|
||||||
|
if (this.props.activeViewport === index) {
|
||||||
|
return 'active';
|
||||||
|
};
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
// Get the height percentage for each viewport
|
||||||
|
const rows = this.props.rows || 1;
|
||||||
|
const height = 100 / rows;
|
||||||
|
|
||||||
|
// Get the width percentage for each viewport
|
||||||
|
const columns = this.props.columns || 1;
|
||||||
|
const width = 100 / columns;
|
||||||
|
|
||||||
|
let viewportData = this.props.viewportData;
|
||||||
|
const numViewports = rows * columns;
|
||||||
|
const numViewportsWithData = viewportData.length;
|
||||||
|
|
||||||
|
// Check if the viewportData length is different from the given
|
||||||
|
if (numViewportsWithData < numViewports) {
|
||||||
|
// Add the missing viewports
|
||||||
|
const difference = numViewports - numViewportsWithData;
|
||||||
|
for (let i = 0; i < difference; i++) {
|
||||||
|
viewportData.push({
|
||||||
|
viewportIndex: numViewportsWithData + i + 1,
|
||||||
|
rows,
|
||||||
|
columns
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (numViewportsWithData > numViewports) {
|
||||||
|
// Remove the additional viewports
|
||||||
|
viewportData = viewportData.slice(0, numViewports);
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewports = viewportData.map((viewport, index) => {
|
||||||
|
const className = `viewportContainer ${this.getClass(index)} ${getActiveClass(index)}`;
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
height: `${height}%`,
|
||||||
|
width: `${width}%`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const cornerstoneViewport = (viewport) => (
|
||||||
|
<div className="removable">
|
||||||
|
<CornerstoneViewport/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const pluginViewport = (viewport) => (
|
||||||
|
<div className={`viewport-plugin-${viewport.plugin}`} style="height:100%; width: 100%">
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} style={styles}>
|
||||||
|
{viewport.plugin === 'cornerstone' ?
|
||||||
|
cornerstoneViewport(viewport) :
|
||||||
|
pluginViewport(viewport)
|
||||||
|
}
|
||||||
|
{/*{>seriesQuickSwitch (clone this viewport=viewport viewportIndex=@index)}*/}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id='imageViewerViewports'>
|
||||||
|
{ viewports }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GridLayout;
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
@value globals: "../GlobalStyles.css";
|
||||||
|
@value activeColor, imageSliderColor from globals;
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 15px;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll .scroll-holder {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll > .scroll-holder > .imageSlider {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
transform-origin: top left;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%; /* Note: This width does not have an effect. */
|
||||||
|
margin-left: 10px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.imageSlider:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.imageSlider::-moz-focus-outer {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.imageSlider::-webkit-slider-thumb {
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
background: imageSliderColor;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 45px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.imageSlider::-webkit-slider-thumb:active {
|
||||||
|
background: activeColor;
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import './ImageScrollbar.css';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class ImageScrollbar extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.onChange = this.onChange.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.style = {
|
||||||
|
width: `${this.props.height}`
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="scroll">
|
||||||
|
<div className="scroll-holder">
|
||||||
|
<input
|
||||||
|
className="imageSlider"
|
||||||
|
style={this.style}
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max={this.props.max}
|
||||||
|
step="1"
|
||||||
|
value={this.props.value}
|
||||||
|
onChange={this.onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(event) {
|
||||||
|
const intValue = parseInt(event.target.value, 10);
|
||||||
|
this.props.onInputCallback(intValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageScrollbar.propTypes = {
|
||||||
|
value: PropTypes.number.isRequired,
|
||||||
|
onInputCallback: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImageScrollbar;
|
||||||
@ -1,8 +1,12 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { Template } from 'meteor/templating';
|
|
||||||
import { $ } from 'meteor/jquery';
|
import { $ } from 'meteor/jquery';
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
|
||||||
|
import { Component } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import './LoadingIndicator.styl';
|
||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
// This checking is necessary because cornerstoneTools may not have some tools available.
|
// This checking is necessary because cornerstoneTools may not have some tools available.
|
||||||
// Example: when an app defines its own cornerstone's lib versions, so it
|
// Example: when an app defines its own cornerstone's lib versions, so it
|
||||||
@ -69,11 +73,38 @@ const errorLoadingHandler = (element, imageId, error, source) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Template.loadingIndicator.helpers({
|
class LoadingIndicator extends Component {
|
||||||
'percentComplete'() {
|
constructor(props) {
|
||||||
const percentComplete = Session.get('CornerstoneLoadProgress' + this.viewportIndex);
|
super(props);
|
||||||
if (percentComplete && percentComplete !== 100) {
|
|
||||||
return `${percentComplete}%`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
render() {
|
||||||
|
// TODO[react]: Pass this in as a prop reactively
|
||||||
|
//const percentComplete = Session.get('CornerstoneLoadProgress' + this.viewportIndex);
|
||||||
|
let percComplete;
|
||||||
|
if (this.props.percentComplete && this.props.percentComplete !== 100) {
|
||||||
|
percComplete = `${this.props.percentComplete}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
<div className="imageViewerLoadingIndicator loadingIndicator">
|
||||||
|
<div className="indicatorContents">
|
||||||
|
<p>Loading... <i className="fa fa-spin fa-circle-o-notch fa-fw"></i> {{percComplete}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="imageViewerErrorLoadingIndicator loadingIndicator">
|
||||||
|
<div className="indicatorContents">
|
||||||
|
<h4>Error Loading Image</h4>
|
||||||
|
<p className='description'>An error has occurred.</p>
|
||||||
|
<p className='details'></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadingIndicator.propTypes = {
|
||||||
|
percentComplete: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoadingIndicator;
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
@value globals: "../GlobalStyles.css";
|
||||||
|
@value textActiveColor, uiGrayDark, uiGrayLight from globals;
|
||||||
|
|
||||||
|
.ToolContextMenu {
|
||||||
|
position: absolute;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 5px;
|
||||||
|
z-index: 1000;
|
||||||
|
display: block;
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolContextMenu > ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolContextMenu > ul > li > button {
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
border: none;
|
||||||
|
color: uiGrayLight;
|
||||||
|
border-radius: 3px;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolContextMenu > ul > li > button:hover {
|
||||||
|
color: uiGrayDark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToolContextMenu > ul > li > button:active {
|
||||||
|
color: textActiveColor;
|
||||||
|
}
|
||||||
@ -0,0 +1,264 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import './ViewportOverlay.css';
|
||||||
|
/*import * as cornerstone from 'cornerstone-core';
|
||||||
|
import * as cornerstoneTools from 'cornerstone-tools';*/
|
||||||
|
import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import './ToolContextMenu.css';
|
||||||
|
|
||||||
|
const toolTypes = ['Bidirectional'];
|
||||||
|
|
||||||
|
let defaultDropdownItems = [
|
||||||
|
{
|
||||||
|
actionType: 'Delete',
|
||||||
|
action: ({ nearbyToolData, eventData }) => {
|
||||||
|
const element = eventData.element;
|
||||||
|
|
||||||
|
cornerstoneTools.removeToolState(
|
||||||
|
element,
|
||||||
|
nearbyToolData.toolType,
|
||||||
|
nearbyToolData.tool
|
||||||
|
);
|
||||||
|
cornerstone.updateImage(element);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actionType: 'bidirectionalLabel',
|
||||||
|
action: ({ nearbyToolData, eventData }) => {
|
||||||
|
const element = eventData.element;
|
||||||
|
const { tool, toolType } = nearbyToolData;
|
||||||
|
|
||||||
|
const doneCallback = () => {
|
||||||
|
cornerstone.updateImage(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
skipButton: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const ToolInstance = cornerstoneTools.getToolForElement(
|
||||||
|
element,
|
||||||
|
toolType
|
||||||
|
);
|
||||||
|
ToolInstance.configuration.getMeasurementLocationCallback(
|
||||||
|
tool,
|
||||||
|
eventData,
|
||||||
|
doneCallback,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
actionType: 'bidirectionalDescription',
|
||||||
|
action: ({ nearbyToolData, eventData }) => {
|
||||||
|
const element = eventData.element;
|
||||||
|
const { tool, toolType } = nearbyToolData;
|
||||||
|
|
||||||
|
const doneCallback = () => {
|
||||||
|
cornerstone.updateImage(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
skipButton: true,
|
||||||
|
editDescription: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const ToolInstance = cornerstoneTools.getToolForElement(
|
||||||
|
element,
|
||||||
|
toolType
|
||||||
|
);
|
||||||
|
ToolInstance.configuration.getMeasurementLocationCallback(
|
||||||
|
tool,
|
||||||
|
eventData,
|
||||||
|
doneCallback,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function getNearbyToolData(element, coords, toolTypes) {
|
||||||
|
const nearbyTool = {};
|
||||||
|
let pointNearTool = false;
|
||||||
|
|
||||||
|
toolTypes.forEach(toolType => {
|
||||||
|
const toolData = cornerstoneTools.getToolState(element, toolType);
|
||||||
|
if (!toolData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toolData.data.forEach(function(data, index) {
|
||||||
|
const toolInterface = cornerstoneTools[`${toolType}Tool`];
|
||||||
|
|
||||||
|
// TODO: vNext wants me to do this?
|
||||||
|
// Switch to cornerstoneTools.store.state.tools
|
||||||
|
const t = new toolInterface();
|
||||||
|
|
||||||
|
if (t.pointNearTool(element, data, coords)) {
|
||||||
|
pointNearTool = true;
|
||||||
|
nearbyTool.tool = data;
|
||||||
|
nearbyTool.index = index;
|
||||||
|
nearbyTool.toolType = toolType;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pointNearTool) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pointNearTool ? nearbyTool : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDropdownItems(eventData, isTouchEvent = false) {
|
||||||
|
const nearbyToolData = getNearbyToolData(
|
||||||
|
eventData.element,
|
||||||
|
eventData.currentPoints.canvas,
|
||||||
|
toolTypes
|
||||||
|
);
|
||||||
|
|
||||||
|
// Annotate tools for touch events already have a press handle to edit it, has a better UX for deleting it
|
||||||
|
if (
|
||||||
|
isTouchEvent &&
|
||||||
|
nearbyToolData &&
|
||||||
|
nearbyToolData.toolType === 'arrowAnnotate'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dropdownItems = [];
|
||||||
|
if (nearbyToolData) {
|
||||||
|
defaultDropdownItems.forEach(function(item) {
|
||||||
|
item.params = {
|
||||||
|
eventData,
|
||||||
|
nearbyToolData
|
||||||
|
};
|
||||||
|
|
||||||
|
if (item.actionType === 'Delete') {
|
||||||
|
item.text = 'Delete measurement';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.actionType === 'bidirectionalLabel') {
|
||||||
|
if (nearbyToolData.toolType !== 'Bidirectional') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item.text = `${nearbyToolData.tool.location ? 'Edit' : 'Add'} Label`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.actionType === 'bidirectionalDescription') {
|
||||||
|
if (
|
||||||
|
nearbyToolData.toolType !== 'Bidirectional' ||
|
||||||
|
!nearbyToolData.tool.location
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item.text = `${
|
||||||
|
nearbyToolData.tool.description ? 'Edit' : 'Add'
|
||||||
|
} Description`;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdownItems.push(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return dropdownItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ToolContextMenu extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.mainElement = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.props.toolContextMenuData) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { eventData, isTouchEvent } = this.props.toolContextMenuData;
|
||||||
|
const dropdownItems = getDropdownItems(eventData, isTouchEvent);
|
||||||
|
|
||||||
|
// Skip if there is no dropdown item
|
||||||
|
if (!dropdownItems.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropdownComponents = dropdownItems.map(item => {
|
||||||
|
const itemOnClick = event => {
|
||||||
|
item.action(item.params);
|
||||||
|
this.props.onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={item.actionType}>
|
||||||
|
<button className="form-action" onClick={itemOnClick}>
|
||||||
|
<span key={item.actionType}>{item.text}</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const position = {
|
||||||
|
top: `${eventData.currentPoints.canvas.y}px`,
|
||||||
|
left: `${eventData.currentPoints.canvas.x}px`
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="ToolContextMenu dropdown"
|
||||||
|
style={position}
|
||||||
|
ref={this.mainElement}
|
||||||
|
>
|
||||||
|
<ul className="dropdown-menu dropdown-menu-left bounded">
|
||||||
|
{dropdownComponents}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
if (this.mainElement.current) {
|
||||||
|
this.updateElementPosition();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidUpdate = () => {
|
||||||
|
if (this.mainElement.current) {
|
||||||
|
this.updateElementPosition();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateElementPosition = () => {
|
||||||
|
const {
|
||||||
|
offsetParent,
|
||||||
|
offsetTop,
|
||||||
|
offsetHeight,
|
||||||
|
offsetWidth,
|
||||||
|
offsetLeft
|
||||||
|
} = this.mainElement.current;
|
||||||
|
const { eventData } = this.props.toolContextMenuData;
|
||||||
|
if (offsetTop + offsetHeight > offsetParent.offsetHeight) {
|
||||||
|
const offBoundPixels =
|
||||||
|
offsetTop + offsetHeight - offsetParent.offsetHeight;
|
||||||
|
const top = eventData.currentPoints.canvas.y - offBoundPixels;
|
||||||
|
|
||||||
|
this.mainElement.current.style.top = `${top > 0 ? top : 0}px`;
|
||||||
|
}
|
||||||
|
if (offsetLeft + offsetWidth > offsetParent.offsetWidth) {
|
||||||
|
const offBoundPixels =
|
||||||
|
offsetLeft + offsetWidth - offsetParent.offsetWidth;
|
||||||
|
const left = eventData.currentPoints.canvas.x - offBoundPixels;
|
||||||
|
|
||||||
|
this.mainElement.current.style.left = `${left > 0 ? left : 0}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolContextMenu.propTypes = {
|
||||||
|
toolContextMenuData: PropTypes.object, // or null
|
||||||
|
onClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToolContextMenu;
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Session } from 'meteor/session';
|
||||||
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
// Local Modules
|
||||||
|
import { unloadHandlers } from '../../../lib/unloadHandlers';
|
||||||
|
import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManager';
|
||||||
|
import { LayoutManager } from '../../../lib/classes/LayoutManager';
|
||||||
|
import { StudyPrefetcher } from '../../../lib/classes/StudyPrefetcher';
|
||||||
|
import { StudyLoadingListener } from '../../../lib/classes/StudyLoadingListener';
|
||||||
|
import './ViewerMain.styl';
|
||||||
|
|
||||||
|
import { Component } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
Meteor.startup(() => {
|
||||||
|
window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
|
||||||
|
|
||||||
|
// Set initial value for OHIFViewerMainRendered
|
||||||
|
// session variable. This can used in viewer main template
|
||||||
|
Session.set('OHIFViewerMainRendered', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
class ViewerMain extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
contents: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setContents = this.setContents.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// Attach the Window resize listener
|
||||||
|
// Don't use jQuery here. "window.onresize" will always be null
|
||||||
|
// If its necessary, check all the code for window.onresize getter
|
||||||
|
// and change it to jQuery._data(window, 'events')['resize'].
|
||||||
|
// Otherwise this function will be probably overrided.
|
||||||
|
// See cineDialog instance.setResizeHandler function
|
||||||
|
window.addEventListener('resize', window.ResizeViewportManager.getResizeHandler());
|
||||||
|
|
||||||
|
// Add beforeUnload event handler to check for unsaved changes
|
||||||
|
window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
|
||||||
|
|
||||||
|
// Set the current context
|
||||||
|
OHIF.context.set('viewer');
|
||||||
|
|
||||||
|
const { studies } = this.props;
|
||||||
|
this.studyPrefetcher = StudyPrefetcher.getInstance();
|
||||||
|
this.studyLoadingListener = StudyLoadingListener.getInstance();
|
||||||
|
this.studyLoadingListener.clear();
|
||||||
|
this.studyLoadingListener.addStudies(studies);
|
||||||
|
|
||||||
|
OHIF.viewerbase.layoutManager = new LayoutManager(this.setContents, studies);
|
||||||
|
this.studyPrefetcher.setStudies(studies);
|
||||||
|
|
||||||
|
Session.set('OHIFViewerMainRendered', Math.random());
|
||||||
|
}
|
||||||
|
|
||||||
|
setContents(component, data) {
|
||||||
|
const Comp = this.props.component;
|
||||||
|
const contents = (<Comp data={data}/>);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
contents
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="viewerMain">
|
||||||
|
{this.state.contents}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
OHIF.log.info('viewerMain onDestroyed');
|
||||||
|
|
||||||
|
// Remove the Window resize listener
|
||||||
|
window.removeEventListener('resize', window.ResizeViewportManager.getResizeHandler());
|
||||||
|
|
||||||
|
// Remove beforeUnload event handler...
|
||||||
|
window.removeEventListener('beforeunload', unloadHandlers.beforeUnload);
|
||||||
|
|
||||||
|
// Destroy the synchronizer used to update reference lines
|
||||||
|
OHIF.viewer.updateImageSynchronizer.destroy();
|
||||||
|
|
||||||
|
delete OHIF.viewerbase.layoutManager;
|
||||||
|
ProtocolEngine = null;
|
||||||
|
|
||||||
|
Session.set('OHIFViewerMainRendered', false);
|
||||||
|
|
||||||
|
// Stop prefetching when we close the viewer
|
||||||
|
this.studyPrefetcher.destroy();
|
||||||
|
|
||||||
|
// Destroy stack loading listeners when we close the viewer
|
||||||
|
this.studyLoadingListener.clear();
|
||||||
|
|
||||||
|
// Clear references to all stacks in the StackManager
|
||||||
|
OHIF.viewerbase.stackManager.clearStacks();
|
||||||
|
|
||||||
|
// @TypeSafeStudies
|
||||||
|
// Clears OHIF.viewer.Studies collection
|
||||||
|
OHIF.viewer.Studies.removeAll();
|
||||||
|
|
||||||
|
// @TypeSafeStudies
|
||||||
|
// Clears OHIF.viewer.StudyMetadataList collection
|
||||||
|
OHIF.viewer.StudyMetadataList.removeAll();
|
||||||
|
|
||||||
|
// Reset the current context
|
||||||
|
OHIF.context.set(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewerMain.propTypes = {
|
||||||
|
studies: PropTypes.array.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ViewerMain;
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
.ViewportOverlay {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewportOverlay .overlay-element {
|
||||||
|
position: absolute;
|
||||||
|
color: lightblue;
|
||||||
|
text-shadow: 1px 1px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewportOverlay .overlay-element > span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewportOverlay .top-right {
|
||||||
|
top: 10px;
|
||||||
|
right: 25px; /* room for scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewportOverlay .top-left {
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewportOverlay .bottom-right {
|
||||||
|
bottom: 10px;
|
||||||
|
right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ViewportOverlay .bottom-left {
|
||||||
|
bottom: 10px;
|
||||||
|
left: 10px;
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
import './ViewportOverlay.css';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class ViewportOverlay extends Component {
|
||||||
|
render() {
|
||||||
|
const scale = Math.round(this.props.viewport.scale * 100) / 100;
|
||||||
|
const imageId = this.props.imageId;
|
||||||
|
|
||||||
|
/*const patientId = cornerstone.metaData.get('00100020', imageId);
|
||||||
|
const studyDate = cornerstone.metaData.get('00080020', imageId);
|
||||||
|
const collection = cornerstone.metaData.get('00131010', imageId);
|
||||||
|
const studyDescription = cornerstone.metaData.get('00081030', imageId);*/
|
||||||
|
|
||||||
|
const windowWidth = Math.round(this.props.viewport.voi.windowWidth);
|
||||||
|
const windowCenter = Math.round(this.props.viewport.voi.windowCenter);
|
||||||
|
const imagesLeft =
|
||||||
|
this.props.stack.imageIds.length - this.props.numImagesLoaded;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="ViewportOverlay">
|
||||||
|
<div className="top-left overlay-element">
|
||||||
|
<span>{this.props.patientId}</span>
|
||||||
|
<span>{this.props.studyDate}</span>
|
||||||
|
<span>{this.props.studyDescription}</span>
|
||||||
|
<span>
|
||||||
|
{imagesLeft > 0 ? `${imagesLeft} images remaining...` : ''}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="bottom-left overlay-element">Zoom: {scale}</div>
|
||||||
|
<div className="bottom-right overlay-element">
|
||||||
|
<span>
|
||||||
|
WW/WC: {windowWidth} / {windowCenter}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Image: {this.props.stack.currentImageIdIndex + 1} /{' '}
|
||||||
|
{this.props.stack.imageIds.length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewportOverlay.propTypes = {
|
||||||
|
viewport: PropTypes.object.isRequired,
|
||||||
|
imageId: PropTypes.string.isRequired,
|
||||||
|
stack: PropTypes.object.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewportOverlay;
|
||||||
@ -1,18 +0,0 @@
|
|||||||
<template name="gridLayout">
|
|
||||||
<div id='imageViewerViewports'>
|
|
||||||
{{#each viewport in viewports}}
|
|
||||||
<div class="viewportContainer {{getClass @index}} {{ activeClass @index }}" style="height:{{height}}%;width:{{width}}%;">
|
|
||||||
{{ #if eq viewport.plugin 'cornerstone'}}
|
|
||||||
<div class="removable">
|
|
||||||
{{>imageViewerViewport (clone viewport)}}
|
|
||||||
</div>
|
|
||||||
{{ else }}
|
|
||||||
<div class="viewport-plugin-{{viewport.plugin}}"
|
|
||||||
style="height:100%; width: 100%">
|
|
||||||
</div>
|
|
||||||
{{ /if }}
|
|
||||||
{{>seriesQuickSwitch (clone this viewport=viewport viewportIndex=@index)}}
|
|
||||||
</div>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
import { OHIF } from 'meteor/ohif:core';
|
|
||||||
import { Template } from 'meteor/templating';
|
|
||||||
|
|
||||||
const TOP_CLASS = 'top';
|
|
||||||
const BOTTOM_CLASS = 'bottom';
|
|
||||||
const MIDDLE_CLASS = 'middle';
|
|
||||||
|
|
||||||
Template.gridLayout.helpers({
|
|
||||||
// Get the height percentage for each viewport
|
|
||||||
height() {
|
|
||||||
const instance = Template.instance();
|
|
||||||
const rows = instance.data.rows || 1;
|
|
||||||
return 100 / rows;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get the width percentage for each viewport
|
|
||||||
width() {
|
|
||||||
const instance = Template.instance();
|
|
||||||
const columns = instance.data.columns || 1;
|
|
||||||
return 100 / columns;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get class for each viewport, so each app
|
|
||||||
// using ohif-viewerbase can style on their own
|
|
||||||
getClass(index) {
|
|
||||||
const { rows, columns } = this;
|
|
||||||
|
|
||||||
if (rows === 1) {
|
|
||||||
return `${TOP_CLASS} ${BOTTOM_CLASS}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actualRow = Math.floor(index / columns);
|
|
||||||
|
|
||||||
if ( actualRow === 0 ) {
|
|
||||||
return TOP_CLASS;
|
|
||||||
}
|
|
||||||
if ( actualRow + 1 === rows ) {
|
|
||||||
return BOTTOM_CLASS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MIDDLE_CLASS;
|
|
||||||
},
|
|
||||||
|
|
||||||
activeClass(index) {
|
|
||||||
if (Session.get('activeViewport') === index) {
|
|
||||||
return 'active';
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// Return the viewports list
|
|
||||||
viewports() {
|
|
||||||
const instance = Template.instance();
|
|
||||||
const rows = instance.data.rows;
|
|
||||||
const columns = instance.data.columns;
|
|
||||||
const numViewports = rows * columns;
|
|
||||||
const viewportData = instance.data.viewportData;
|
|
||||||
const numViewportsWithData = viewportData.length;
|
|
||||||
|
|
||||||
// Check if the viewportData length is different from the given
|
|
||||||
if (numViewportsWithData < numViewports) {
|
|
||||||
// Add the missing viewports
|
|
||||||
var difference = numViewports - numViewportsWithData;
|
|
||||||
for (var i = 0; i < difference; i++) {
|
|
||||||
viewportData.push({
|
|
||||||
viewportIndex: numViewportsWithData + i + 1,
|
|
||||||
rows,
|
|
||||||
columns
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (numViewportsWithData > numViewports) {
|
|
||||||
// Remove the additional viewports
|
|
||||||
return viewportData.slice(0, numViewports);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the viewports
|
|
||||||
return viewportData;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -5,6 +5,10 @@ import { OHIF } from 'meteor/ohif:core';
|
|||||||
import { setActiveViewport } from '../../../lib/setActiveViewport';
|
import { setActiveViewport } from '../../../lib/setActiveViewport';
|
||||||
import { switchToImageByIndex } from '../../../lib/switchToImageByIndex';
|
import { switchToImageByIndex } from '../../../lib/switchToImageByIndex';
|
||||||
|
|
||||||
|
import { Component } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
|
||||||
const slideTimeoutTime = 40;
|
const slideTimeoutTime = 40;
|
||||||
let slideTimeout;
|
let slideTimeout;
|
||||||
|
|
||||||
|
|||||||
17
Packages/ohif-viewerbase/client/components/viewer/index.js
Normal file
17
Packages/ohif-viewerbase/client/components/viewer/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import ViewerMain from './ViewerMain/ViewerMain.js';
|
||||||
|
import GridLayout from './GridLayout/GridLayout.js';
|
||||||
|
import LoadingIndicator from './LoadingIndicator/LoadingIndicator.js';
|
||||||
|
import CornerstoneViewport from './CornerstoneViewport.js';
|
||||||
|
import ImageScrollbar from './ImageScrollbar.js';
|
||||||
|
import ToolContextMenu from './ToolContextMenu.js';
|
||||||
|
import ViewportOverlay from './ViewportOverlay.js';
|
||||||
|
|
||||||
|
export {
|
||||||
|
ViewerMain,
|
||||||
|
GridLayout,
|
||||||
|
LoadingIndicator,
|
||||||
|
CornerstoneViewport,
|
||||||
|
ImageScrollbar,
|
||||||
|
ToolContextMenu,
|
||||||
|
ViewportOverlay,
|
||||||
|
};
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<template name="loadingIndicator">
|
|
||||||
<div class="imageViewerLoadingIndicator loadingIndicator">
|
|
||||||
<div class="indicatorContents">
|
|
||||||
<p>Loading... <i class="fa fa-spin fa-circle-o-notch fa-fw"></i> {{percentComplete}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="imageViewerErrorLoadingIndicator loadingIndicator">
|
|
||||||
<div class="indicatorContents">
|
|
||||||
<h4>Error Loading Image</h4>
|
|
||||||
<p class='description'>An error has occurred.</p>
|
|
||||||
<p class='details'></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -55,6 +55,9 @@ const createDropdown = function(eventData, isTouchEvent = false) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO[react]: Migrate these to ViewerMain React Component
|
||||||
|
|
||||||
Template.viewerMain.events({
|
Template.viewerMain.events({
|
||||||
'cornerstonetoolsmouseclick .imageViewerViewport'(event) {
|
'cornerstonetoolsmouseclick .imageViewerViewport'(event) {
|
||||||
const { originalEvent } = event;
|
const { originalEvent } = event;
|
||||||
@ -69,4 +72,4 @@ Template.viewerMain.events({
|
|||||||
const eventData = originalEvent.detail;
|
const eventData = originalEvent.detail;
|
||||||
createDropdown(eventData, true);
|
createDropdown(eventData, true);
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
<template name="viewerMain">
|
|
||||||
<div class="viewerMain">
|
|
||||||
<div id='layoutManagerTarget'>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
import { Template } from 'meteor/templating';
|
|
||||||
import { Session } from 'meteor/session';
|
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
|
||||||
// Local Modules
|
|
||||||
import { unloadHandlers } from '../../../lib/unloadHandlers';
|
|
||||||
import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManager';
|
|
||||||
import { LayoutManager } from '../../../lib/classes/LayoutManager';
|
|
||||||
import { StudyPrefetcher } from '../../../lib/classes/StudyPrefetcher';
|
|
||||||
import { StudyLoadingListener } from '../../../lib/classes/StudyLoadingListener';
|
|
||||||
|
|
||||||
Meteor.startup(() => {
|
|
||||||
window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
|
|
||||||
|
|
||||||
// Set initial value for OHIFViewerMainRendered
|
|
||||||
// session variable. This can used in viewer main template
|
|
||||||
Session.set('OHIFViewerMainRendered', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.viewerMain.onCreated(() => {
|
|
||||||
// Attach the Window resize listener
|
|
||||||
// Don't use jQuery here. "window.onresize" will always be null
|
|
||||||
// If its necessary, check all the code for window.onresize getter
|
|
||||||
// and change it to jQuery._data(window, 'events')['resize'].
|
|
||||||
// Otherwise this function will be probably overrided.
|
|
||||||
// See cineDialog instance.setResizeHandler function
|
|
||||||
window.addEventListener('resize', window.ResizeViewportManager.getResizeHandler());
|
|
||||||
|
|
||||||
// Add beforeUnload event handler to check for unsaved changes
|
|
||||||
window.addEventListener('beforeunload', unloadHandlers.beforeUnload);
|
|
||||||
|
|
||||||
// Set the current context
|
|
||||||
OHIF.context.set('viewer');
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.viewerMain.onRendered(() => {
|
|
||||||
const instance = Template.instance();
|
|
||||||
const { studies } = instance.data;
|
|
||||||
const parentElement = instance.$('#layoutManagerTarget').get(0);
|
|
||||||
const studyPrefetcher = StudyPrefetcher.getInstance();
|
|
||||||
instance.studyPrefetcher = studyPrefetcher;
|
|
||||||
|
|
||||||
instance.studyLoadingListener = StudyLoadingListener.getInstance();
|
|
||||||
instance.studyLoadingListener.clear();
|
|
||||||
instance.studyLoadingListener.addStudies(studies);
|
|
||||||
|
|
||||||
OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies);
|
|
||||||
studyPrefetcher.setStudies(studies);
|
|
||||||
|
|
||||||
Session.set('OHIFViewerMainRendered', Math.random());
|
|
||||||
});
|
|
||||||
|
|
||||||
Template.viewerMain.onDestroyed(() => {
|
|
||||||
const instance = Template.instance();
|
|
||||||
|
|
||||||
OHIF.log.info('viewerMain onDestroyed');
|
|
||||||
|
|
||||||
// Remove the Window resize listener
|
|
||||||
window.removeEventListener('resize', window.ResizeViewportManager.getResizeHandler());
|
|
||||||
|
|
||||||
// Remove beforeUnload event handler...
|
|
||||||
window.removeEventListener('beforeunload', unloadHandlers.beforeUnload);
|
|
||||||
|
|
||||||
// Destroy the synchronizer used to update reference lines
|
|
||||||
OHIF.viewer.updateImageSynchronizer.destroy();
|
|
||||||
|
|
||||||
delete OHIF.viewerbase.layoutManager;
|
|
||||||
ProtocolEngine = null;
|
|
||||||
|
|
||||||
Session.set('OHIFViewerMainRendered', false);
|
|
||||||
|
|
||||||
// Stop prefetching when we close the viewer
|
|
||||||
instance.studyPrefetcher.destroy();
|
|
||||||
|
|
||||||
// Destroy stack loading listeners when we close the viewer
|
|
||||||
instance.studyLoadingListener.clear();
|
|
||||||
|
|
||||||
// Clear references to all stacks in the StackManager
|
|
||||||
OHIF.viewerbase.stackManager.clearStacks();
|
|
||||||
|
|
||||||
// @TypeSafeStudies
|
|
||||||
// Clears OHIF.viewer.Studies collection
|
|
||||||
OHIF.viewer.Studies.removeAll();
|
|
||||||
|
|
||||||
// @TypeSafeStudies
|
|
||||||
// Clears OHIF.viewer.StudyMetadataList collection
|
|
||||||
OHIF.viewer.StudyMetadataList.removeAll();
|
|
||||||
|
|
||||||
// Reset the current context
|
|
||||||
OHIF.context.set(null);
|
|
||||||
});
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
//import PropTypes from 'prop-types';
|
||||||
|
import './ViewportOrientationMarkers.styl';
|
||||||
|
|
||||||
|
class ViewportOrientationMarkers extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="viewportOrientationMarkers noselect">
|
||||||
|
<div class="topMid orientationMarker">
|
||||||
|
</div>
|
||||||
|
<div class="leftMid orientationMarker">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewportOrientationMarkers;
|
||||||
@ -1,8 +0,0 @@
|
|||||||
<template name="viewportOrientationMarkers">
|
|
||||||
<div class="viewportOrientationMarkers noselect">
|
|
||||||
<div class="topMid orientationMarker">
|
|
||||||
</div>
|
|
||||||
<div class="leftMid orientationMarker">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@ -6,6 +6,9 @@ import { Viewerbase } from '../namespace';
|
|||||||
|
|
||||||
import './collections';
|
import './collections';
|
||||||
import './lib/debugReactivity';
|
import './lib/debugReactivity';
|
||||||
|
import components from './components/index.js';
|
||||||
|
|
||||||
|
Viewerbase.components = components;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exported Functions
|
* Exported Functions
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import { $ } from 'meteor/jquery';
|
|||||||
|
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
|
|
||||||
|
import viewer from '../../components/viewer';
|
||||||
|
|
||||||
const PLUGIN_CORNERSTONE = 'cornerstone';
|
const PLUGIN_CORNERSTONE = 'cornerstone';
|
||||||
|
|
||||||
let isInteractingWithViewport = false;
|
let isInteractingWithViewport = false;
|
||||||
@ -28,14 +30,15 @@ export class LayoutManager {
|
|||||||
* @param {DOM element} parentNode DOM element representing the parent node, which wraps the Layout Manager content
|
* @param {DOM element} parentNode DOM element representing the parent node, which wraps the Layout Manager content
|
||||||
* @param {Array} studies Array of studies objects that will be rendered in the Viewer. Each object will be rendered in a div.imageViewerViewport
|
* @param {Array} studies Array of studies objects that will be rendered in the Viewer. Each object will be rendered in a div.imageViewerViewport
|
||||||
*/
|
*/
|
||||||
constructor(parentNode, studies) {
|
constructor(setContents, studies) {
|
||||||
OHIF.log.info('LayoutManager constructor');
|
OHIF.log.info('LayoutManager constructor');
|
||||||
|
|
||||||
|
this.setContents = setContents;
|
||||||
this.observer = new Tracker.Dependency();
|
this.observer = new Tracker.Dependency();
|
||||||
this.parentNode = parentNode;
|
this.parentNode = parentNode;
|
||||||
this.studies = studies;
|
this.studies = studies;
|
||||||
this.viewportData = [];
|
this.viewportData = [];
|
||||||
this.layoutTemplateName = 'gridLayout';
|
this.layoutTemplate = viewer.GridLayout;
|
||||||
this.layoutProps = {
|
this.layoutProps = {
|
||||||
rows: 1,
|
rows: 1,
|
||||||
columns: 1
|
columns: 1
|
||||||
@ -209,11 +212,10 @@ export class LayoutManager {
|
|||||||
data.viewportData.push(viewportDataAndLayoutProps);
|
data.viewportData.push(viewportDataAndLayoutProps);
|
||||||
});
|
});
|
||||||
|
|
||||||
const layoutTemplate = Template[this.layoutTemplateName];
|
|
||||||
|
|
||||||
this.parentNode.innerHTML = '';
|
|
||||||
this.updateLayoutClass();
|
this.updateLayoutClass();
|
||||||
Blaze.renderWithData(layoutTemplate, data, this.parentNode);
|
|
||||||
|
const component = GridLayout;
|
||||||
|
this.setContents(component, data)
|
||||||
|
|
||||||
this.updateSession();
|
this.updateSession();
|
||||||
|
|
||||||
@ -276,6 +278,8 @@ export class LayoutManager {
|
|||||||
container.appendChild(newViewportContainer);
|
container.appendChild(newViewportContainer);
|
||||||
|
|
||||||
Blaze.renderWithData(Template.imageViewerViewport, data, newViewportContainer);
|
Blaze.renderWithData(Template.imageViewerViewport, data, newViewportContainer);
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
newViewportContainer.className = `viewport-plugin-${plugin}`;
|
newViewportContainer.className = `viewport-plugin-${plugin}`;
|
||||||
newViewportContainer.style.width = '100%';
|
newViewportContainer.style.width = '100%';
|
||||||
@ -314,10 +318,8 @@ export class LayoutManager {
|
|||||||
columns: 1
|
columns: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const layoutTemplate = Template.gridLayout;
|
const component = GridLayout;
|
||||||
|
this.setContents(component, data)
|
||||||
this.parentNode.innerHTML = '';
|
|
||||||
Blaze.renderWithData(layoutTemplate, data, this.parentNode);
|
|
||||||
|
|
||||||
this.isZoomed = true;
|
this.isZoomed = true;
|
||||||
this.zoomedViewportIndex = viewportIndex;
|
this.zoomedViewportIndex = viewportIndex;
|
||||||
|
|||||||
@ -42,6 +42,8 @@ export function updateOrientationMarkers(element, viewport) {
|
|||||||
markers.left = cornerstoneTools.orientation.invertOrientationString(markers.left);
|
markers.left = cornerstoneTools.orientation.invertOrientationString(markers.left);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO[react]: This should set values in the redux store
|
||||||
|
|
||||||
// Get the viewport orientation marker DOM elements
|
// Get the viewport orientation marker DOM elements
|
||||||
const viewportOrientationMarkers = $(element).siblings('.viewportOrientationMarkers');
|
const viewportOrientationMarkers = $(element).siblings('.viewportOrientationMarkers');
|
||||||
const topMarker = viewportOrientationMarkers.find('.topMid');
|
const topMarker = viewportOrientationMarkers.find('.topMid');
|
||||||
|
|||||||
@ -125,29 +125,16 @@ Package.onUse(function(api) {
|
|||||||
api.addFiles('client/components/viewer/imageViewerViewport/imageViewerViewport.js', 'client');
|
api.addFiles('client/components/viewer/imageViewerViewport/imageViewerViewport.js', 'client');
|
||||||
api.addFiles('client/components/viewer/imageViewerViewport/imageViewerViewport.styl', 'client');
|
api.addFiles('client/components/viewer/imageViewerViewport/imageViewerViewport.styl', 'client');
|
||||||
|
|
||||||
api.addFiles('client/components/viewer/gridLayout/gridLayout.html', 'client');
|
|
||||||
api.addFiles('client/components/viewer/gridLayout/gridLayout.js', 'client');
|
|
||||||
api.addFiles('client/components/viewer/gridLayout/gridLayout.styl', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/viewer/loadingIndicator/loadingIndicator.html', 'client');
|
|
||||||
api.addFiles('client/components/viewer/loadingIndicator/loadingIndicator.js', 'client');
|
|
||||||
api.addFiles('client/components/viewer/loadingIndicator/loadingIndicator.styl', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/viewer/annotationDialogs/annotationDialogs.html', 'client');
|
api.addFiles('client/components/viewer/annotationDialogs/annotationDialogs.html', 'client');
|
||||||
api.addFiles('client/components/viewer/annotationDialogs/annotationDialogs.js', 'client');
|
api.addFiles('client/components/viewer/annotationDialogs/annotationDialogs.js', 'client');
|
||||||
api.addFiles('client/components/viewer/annotationDialogs/annotationDialogs.styl', 'client');
|
api.addFiles('client/components/viewer/annotationDialogs/annotationDialogs.styl', 'client');
|
||||||
|
|
||||||
api.addFiles('client/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.html', 'client');
|
|
||||||
api.addFiles('client/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.styl', 'client');
|
api.addFiles('client/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.styl', 'client');
|
||||||
|
|
||||||
api.addFiles('client/components/viewer/viewportOverlay/viewportOverlay.html', 'client');
|
api.addFiles('client/components/viewer/viewportOverlay/viewportOverlay.html', 'client');
|
||||||
api.addFiles('client/components/viewer/viewportOverlay/viewportOverlay.js', 'client');
|
api.addFiles('client/components/viewer/viewportOverlay/viewportOverlay.js', 'client');
|
||||||
api.addFiles('client/components/viewer/viewportOverlay/viewportOverlay.styl', 'client');
|
api.addFiles('client/components/viewer/viewportOverlay/viewportOverlay.styl', 'client');
|
||||||
|
|
||||||
api.addFiles('client/components/viewer/viewerMain/viewerMain.html', 'client');
|
|
||||||
api.addFiles('client/components/viewer/viewerMain/viewerMain.js', 'client');
|
|
||||||
api.addFiles('client/components/viewer/viewerMain/viewerMain.styl', 'client');
|
|
||||||
|
|
||||||
api.addFiles('client/components/viewer/toolContextMenu/toolContextMenu.js', 'client');
|
api.addFiles('client/components/viewer/toolContextMenu/toolContextMenu.js', 'client');
|
||||||
|
|
||||||
api.addFiles('client/components/viewer/imageControls/imageControls.html', 'client');
|
api.addFiles('client/components/viewer/imageControls/imageControls.html', 'client');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user