Lesion Tracker Redesign so far

This commit is contained in:
Erik Ziegler 2016-06-07 19:09:47 +02:00
parent b247e89d8b
commit a9a4f3f82b
109 changed files with 2972 additions and 936 deletions

View File

@ -33,6 +33,7 @@ fortawesome:fontawesome
random
reactive-var
reactive-dict
design
lesiontracker
practicalmeteor:loglevel
momentjs:moment

View File

@ -38,6 +38,7 @@ ddp-common@1.2.6
ddp-rate-limiter@1.0.5
ddp-server@1.2.9
deps@1.0.12
design@0.0.1
dicomweb@0.0.1
diff-sequence@1.0.6
dimseservice@0.0.1

View File

@ -1,9 +1,6 @@
body
background-color: black
.tab-content
height: calc(100% - 40px)
/* Mobile devices */
@media only screen and (min-device-width : 375px) and (max-device-width : 768px)
.logoContainer

View File

@ -34,7 +34,6 @@ Meteor.startup(function() {
touch: cornerstoneTools.exToolTouch
});
var states = toolManager.getToolDefaultStates();
states.deactivate.push('bidirectional');
states.deactivate.push('nonTarget');
@ -44,7 +43,5 @@ Meteor.startup(function() {
states.deactivate.push('exTool');
states.activate.push('deleteLesionKeyboardTool');
states.enable.push('scaleOverlayTool');
toolManager.setToolDefaultStates(states);
});

View File

@ -1,35 +0,0 @@
#viewer
width: 100%
height: 100%
margin: 0
padding: 0
background-color: black
#viewportAndLesionTable
height: 100%
width: 99%
background-color: black
float: right
-o-transition: all 0.3s ease-out;
-ms-transition: all 0.3s ease-out;
-moz-transition: all 0.3s ease-out;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
#imageViewerViewports
.viewportContainer
&.active
border: 2px solid #777
&:hover, &:hover.active
border: 2px solid white
#btnCollapse
background-color: #333
border-color: black
&.active
border-color: #333
.btnCollapseIcon
color: white

View File

@ -1,127 +0,0 @@
Template.viewerMain.helpers({
toolbarOptions: function() {
var toolbarOptions = {};
var buttonData = [];
var btnGroup = [];
buttonData.push({
id: 'resetViewport',
title: 'Reset Viewport',
classes: 'imageViewerCommand',
iconClasses: 'fa fa-undo'
});
buttonData.push({
id: 'wwwc',
title: 'WW/WC',
classes: 'imageViewerTool',
iconClasses: 'fa fa-sun-o'
});
buttonData.push({
id: 'zoom',
title: 'Zoom',
classes: 'imageViewerTool',
iconClasses: 'fa fa-search'
});
buttonData.push({
id: 'pan',
title: 'Pan',
classes: 'imageViewerTool',
iconClasses: 'fa fa-arrows'
});
buttonData.push({
id: 'stackScroll',
title: 'Stack Scroll',
classes: 'imageViewerTool',
iconClasses: 'fa fa-bars'
});
buttonData.push({
id: 'length',
title: 'Length Measurement',
classes: 'imageViewerTool',
iconClasses: 'fa fa-arrows-v'
});
buttonData.push({
id: 'ellipticalRoi',
title: 'Elliptical ROI Measurement',
classes: 'imageViewerTool',
iconClasses: 'fa fa-circle-o'
});
buttonData.push({
id: 'bidirectional',
title: 'Target Tool',
classes: 'imageViewerTool',
iconClasses: 'fa fa-arrows-alt'
});
buttonData.push({
id: 'nonTarget',
title: 'Non-Target Tool',
classes: 'imageViewerTool',
iconClasses: 'fa fa-long-arrow-up'
});
buttonData.push({
id: 'clearTools',
title: 'Clear tools',
classes: 'imageViewerCommand',
iconClasses: 'fa fa-trash'
});
buttonData.push({
id: 'toggleLesionTrackerTools',
title: 'Show / hide tools',
classes: 'imageViewerCommand',
iconClasses: 'fa fa-eye'
});
// CR/UN/EX Tools
var crunexToolsBtns = {
id: 'crunexTools',
tools: [{
id: 'crTool',
title: 'CR Tool',
classes: 'imageViewerTool',
iconClasses: 'fa fa-cr'
}, {
id: 'unTool',
title: 'UN Tool',
classes: 'imageViewerTool',
iconClasses: 'fa fa-un'
}, {
id: 'exTool',
title: 'EX Tool',
classes: 'imageViewerTool',
iconClasses: 'fa fa-ex'
}],
title: 'CR/UN/EX',
groupIcon: 'fa fa-exchange'
};
btnGroup.push(crunexToolsBtns);
toolbarOptions.buttonData = buttonData;
toolbarOptions.includePlayClipButton = false;
toolbarOptions.includeLayoutButton = false;
toolbarOptions.includeHangingProtocolButtons = false;
toolbarOptions.btnGroup = btnGroup;
return toolbarOptions;
}
});
Template.viewerMain.onRendered(function() {
var parentNode = document.getElementById('layoutManagerTarget');
var studies = this.data.studies;
layoutManager = new LayoutManager(parentNode, studies);
ProtocolEngine = new HP.ProtocolEngine(layoutManager, studies);
HP.setEngine(ProtocolEngine);
});

View File

@ -13,5 +13,4 @@
<meta name="MobileOptimized" content="320">
<meta name="HandheldFriendly" content="True">
<meta name="apple-mobile-web-app-capable" content="yes">
<link href='http://fonts.googleapis.com/css?family=Sanchez:400,700&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
</head>
</head>

View File

@ -36,11 +36,11 @@ var routerOptions = {
Router.route('/', function() {
// Check user is logged in
if(Meteor.user() && Meteor.userId()) {
if (Meteor.user() && Meteor.userId()) {
if (verifyEmail && Meteor.user().emails && !Meteor.user().emails[0].verified) {
this.render('emailVerification', routerOptions);
} else {
this.render('worklist', routerOptions);
this.render('lesionTracker', routerOptions);
}
} else {
@ -50,11 +50,11 @@ Router.route('/', function() {
Router.route('/worklist', function() {
// Check user is logged in
if(Meteor.user() && Meteor.userId()) {
if (Meteor.user() && Meteor.userId()) {
if (verifyEmail && Meteor.user().emails && !Meteor.user().emails[0].verified) {
this.render('emailVerification', routerOptions);
} else {
this.render('worklist', routerOptions);
this.render('lesionTracker', routerOptions);
}
} else {
@ -77,15 +77,17 @@ Router.route('/viewer/timepoints/:_id', {
return;
}
this.render('worklist', routerOptions);
this.render('lesionTracker', routerOptions);
openNewTabWithTimepoint(timepointId);
}
});
Router.onBeforeAction(function () {
if (!Meteor.userId() && !Meteor.loggingIn()) {
Router.onBeforeAction(function() {
if (!Meteor.userId() && !Meteor.loggingIn()) {
this.render('entrySignIn');
} else {
this.next();
}
},{except: ['entrySignIn', 'entrySignUp', 'forgotPassword', 'resetPassword']});
}, {
except: ['entrySignIn', 'entrySignUp', 'forgotPassword', 'resetPassword']
});

View File

@ -26,6 +26,7 @@ spacebars
check
cornerstone
dicomweb
design
viewerbase
worklist
reactive-var

View File

@ -24,6 +24,7 @@ ddp-client@1.2.9
ddp-common@1.2.6
ddp-server@1.2.9
deps@1.0.12
design@0.0.1
dicomweb@0.0.1
diff-sequence@1.0.6
dimseservice@0.0.1

View File

@ -1,12 +1,11 @@
<head>
<meta charset="utf-8">
<title>OHIF Viewer</title>
<meta name="description" content="Open Health Imaging Foundation DICOM Viewer">
<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">
<!--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">
<link href='http://fonts.googleapis.com/css?family=Sanchez:400,700&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
</head>
</head>

6
Packages/design/app.styl Normal file
View File

@ -0,0 +1,6 @@
@import "{design}/styles/webfonts"
@import "{design}/styles/variables"
@import "{design}/styles/mixins"
@import "{design}/styles/spacings"
@import "{design}/styles/global"

View File

@ -0,0 +1,7 @@
<template name="roundedButtonGroup">
<div class="{{class}} viewerRoundedButtonGroup clearfix center-table">
{{#each item in this.options}}
<a class="viewerRoundedButton noselect {{inlineIf value.get item.key 'active'}}" data-key="{{item.key}}">{{item.text}}</a>
{{/each}}
</div>
</template>

View File

@ -0,0 +1,15 @@
Template.roundedButtonGroup.onRendered(() => {
const instance = Template.instance();
const value = instance.data.value;
if (!value.get()) {
value.set(instance.data.options[0].key);
}
});
Template.roundedButtonGroup.events({
'click [data-key]'(event, instance) {
const $element = $(event.currentTarget);
const key = $element.attr('data-key');
instance.data.value.set(key);
}
});

View File

@ -0,0 +1,46 @@
@import "{design}/app"
$height = 25px
$borderColorActive = #3C8074
$textColorActive = #2D2D2D
.viewerRoundedButtonGroup
font-family: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif
font-weight: 500
position: relative
.viewerRoundedButton
background: $darkUiColor
border: 1px $uiBorderColorDark solid
color: $textSecondaryColor
cursor: pointer
display: inline-block
margin-left: -1px
float: left
font-size: 11px
height: $height
line-height: $height
padding: 0 15px
text-decoration: none
text-transform: uppercase
transition: $sidebarTransition
-webkit-transition: $sidebarTransition
-ms-transition: $sidebarTransition
-o-transition: $sidebarTransition
-moz-transition: $sidebarTransition
z-index: 1
&.active
background: $activeColor
color: $textColorActive
border-color: $borderColorActive
z-index: 2
&:first-child
margin-left: 0
border-bottom-left-radius: $height
border-top-left-radius: $height
&:last-child
border-bottom-right-radius: $height
border-top-right-radius: $height

View File

@ -0,0 +1,34 @@
Package.describe({
name: 'design',
summary: 'OHIF Design styles and components',
version: '0.0.1'
});
Package.onUse(function(api) {
api.versionsFrom('1.2.1');
api.use('ecmascript');
api.use('standard-app-packages');
api.use('jquery');
api.use('stylus');
// Importable colors / typography settings
api.addFiles([
'app.styl',
'styles/global.styl',
'styles/mixins.styl',
'styles/spacings.styl',
'styles/variables.styl',
'styles/webfonts.styl'
], 'client', {
isImport: true
});
// Rounded Button Group
api.addFiles([
'components/roundedButtonGroup/roundedButtonGroup.html',
'components/roundedButtonGroup/roundedButtonGroup.styl',
'components/roundedButtonGroup/roundedButtonGroup.js'
], 'client');
});

View File

@ -0,0 +1,10 @@
html *
font-family: 'Roboto', sans-serif;
hr
border-top: 1px solid $uiBorderColor;
.center-table
display: table
margin-left: auto
margin-right: auto

View File

@ -0,0 +1,13 @@
transition(t)
transition: t
-webkit-transition: t
-ms-transition: t
-o-transition: t
-moz-transition: t
transform(t)
transform: t
-webkit-transform: t
-ms-transform: t
-o-transform: t
-moz-transform: t

View File

@ -0,0 +1,39 @@
$spacer = 1rem
$spacer-x = $spacer
$spacer-y = $spacer
$spacerAxis = $spacer-x,
$spacer-y
$spacings = 0 0,
1 1,
2 1.5,
3 3g
$properties = m margin,
p padding
$sides = t top 1,
r right 0,
b bottom 1,
l left 0
$axes = x left right 0,
y top bottom 1
for $property in $properties
for $spacing in $spacings
.{$property[0]}-a-{$spacing[0]}
{$property[1]} $spacing[1]*$spacer !important
for $side in $sides
.{$property[0]}-{$side[0]}-{$spacing[0]}
{$property[1]}-{$side[1]} $spacing[1]*$spacerAxis[$side[2]] !important
for $axis in $axes
.{$property[0]}-{$axis[0]}-{$spacing[0]}
{$property[1]}-{$axis[1]} $spacing[1]*$spacerAxis[$axis[3]] !important
{$property[1]}-{$axis[2]} $spacing[1]*$spacerAxis[$axis[3]] !important
.m-x-auto
margin-left: auto !important
margin-right: auto !important

View File

@ -0,0 +1,34 @@
// Interface UI Colors
$defaultColor = #94A8B3
$hoverColor = #ffffff
$activeColor = #40D9B8
$uiBorderColor = #436270
$uiBorderColorDark = #3C5D80
$darkUiColor = #16202B
$primaryBackgroundColor = #000000
// Text Colors
$textPrimaryColor = #ffffff
$textSecondaryColor = #91B9CD
// Other Colors
$errorColor = #E29E4A
$menuBackgroundColor = #F2F2F2
// Sizes
$topBarHeight = 30px
$toolbarHeight = 50px
// Thicknesses
$uiBorderThickness = 1px
// Fonts
$logoFontFamily = "Sanchez"
$logoFontWeight = 300
// Transitions
$transitionDuration = 0.3s
$transitionEffect = ease
$sidebarTransition = all $transitionDuration $transitionEffect
$studiesSidebarMenuWidth = 307px
$lesionsSidebarMenuWidth = 317px

View File

@ -0,0 +1,2 @@
@import url(http://fonts.googleapis.com/css?family=Roboto:400,100,100italic,300,300italic,400italic,500,500italic,700,700italic,900,900italic&subset=latin,latin-ext)
@import url(http://fonts.googleapis.com/css?family=Sanchez:400,700&subset=latin,latin-ext)

View File

@ -24,6 +24,10 @@ Array.prototype.move = function(oldIndex, newIndex) {
*/
function getStageIndex(protocol, id) {
var stageIndex;
if (!protocol || !protocol.stages) {
return;
}
protocol.stages.forEach(function(stage, index) {
if (stage.id === id) {
stageIndex = index;

View File

@ -0,0 +1,186 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="icon-hud" viewBox="0 0 20 22">
<title>
HUD
</title>
<path d="M1 2L1 2 1 16C1 16.55 1.44 17 1.99 17L14.01 17C14.55 17 15 16.55 15 16L15 2C15 1.45 14.56 1 14.01 1L1.99 1C1.45 1 1 1.45 1 2L1 2ZM0 2C0 0.89 0.89 0 1.99 0L14.01 0C15.11 0 16 0.89 16 2L16 16C16 17.11 15.11 18 14.01 18L1.99 18C0.89 18 0 17.11 0 16L0 2 0 2Z"/>
<rect x="0" y="0" width="16" height="4" rx="2"/>
<path d="M17.69 3.66C17.91 3.78 18.11 3.95 18.25 4.16 18.4 4.39 18.71 4.45 18.94 4.3 19.17 4.14 19.23 3.83 19.08 3.6 18.84 3.25 18.52 2.97 18.14 2.77 17.9 2.65 17.6 2.74 17.47 2.99 17.34 3.24 17.44 3.54 17.69 3.66L17.69 3.66ZM18.5 6.88L18.5 7.88C18.5 8.15 18.72 8.38 19 8.38 19.28 8.38 19.5 8.15 19.5 7.88L19.5 6.88C19.5 6.6 19.28 6.38 19 6.38 18.72 6.38 18.5 6.6 18.5 6.88L18.5 6.88ZM18.5 10.88L18.5 11.88C18.5 12.15 18.72 12.38 19 12.38 19.28 12.38 19.5 12.15 19.5 11.88L19.5 10.88C19.5 10.6 19.28 10.38 19 10.38 18.72 10.38 18.5 10.6 18.5 10.88L18.5 10.88ZM18.5 14.88L18.5 15.88C18.5 16.15 18.72 16.38 19 16.38 19.28 16.38 19.5 16.15 19.5 15.88L19.5 14.88C19.5 14.6 19.28 14.38 19 14.38 18.72 14.38 18.5 14.6 18.5 14.88L18.5 14.88ZM18.5 18.88L18.5 19C18.5 19.23 18.45 19.44 18.36 19.64 18.24 19.89 18.35 20.19 18.6 20.31 18.85 20.43 19.14 20.32 19.26 20.07 19.42 19.74 19.5 19.38 19.5 19L19.5 18.88C19.5 18.6 19.28 18.38 19 18.38 18.72 18.38 18.5 18.6 18.5 18.88L18.5 18.88ZM16.19 20.5L15.19 20.5C14.91 20.5 14.69 20.72 14.69 21 14.69 21.28 14.91 21.5 15.19 21.5L16.19 21.5C16.46 21.5 16.69 21.28 16.69 21 16.69 20.72 16.46 20.5 16.19 20.5L16.19 20.5ZM12.19 20.5L11.19 20.5C10.91 20.5 10.69 20.72 10.69 21 10.69 21.28 10.91 21.5 11.19 21.5L12.19 21.5C12.46 21.5 12.69 21.28 12.69 21 12.69 20.72 12.46 20.5 12.19 20.5L12.19 20.5ZM8.19 20.5L7.19 20.5C6.91 20.5 6.69 20.72 6.69 21 6.69 21.28 6.91 21.5 7.19 21.5L8.19 21.5C8.46 21.5 8.69 21.28 8.69 21 8.69 20.72 8.46 20.5 8.19 20.5L8.19 20.5ZM4.4 20.38C4.17 20.28 3.97 20.12 3.81 19.92 3.64 19.7 3.33 19.66 3.11 19.83 2.89 20 2.85 20.31 3.02 20.53 3.28 20.86 3.62 21.13 4 21.3 4.26 21.41 4.55 21.29 4.66 21.04 4.77 20.78 4.66 20.49 4.4 20.38L4.4 20.38Z"/>
<g id="lines" transform="translate(4.000000, 7.000000)" stroke-linecap="square">
<path d="M0.4 0.5L7.6 0.5" id="Line"/>
<path d="M0.4 3.5L7.6 3.5" id="Line"/>
<path d="M0.4 6.5L7.6 6.5" id="Line"/>
</g>
</symbol>
<symbol id="icon-measurements-additional" viewBox="0 0 14 13">
<title>
Additional Measurements
</title>
<path d="M0 10.26L0 13 2.66 13 10.36 4.98 7.7 2.24 0 10.26 0 10.26ZM12.39 2.89C12.67 2.6 12.67 2.17 12.39 1.88L10.78 0.22C10.5-0.07 10.08-0.07 9.8 0.22L8.54 1.52 11.2 4.26 12.39 2.89 12.39 2.89ZM6.3 11.56L4.9 13 14 13 14 11.56 6.3 11.56 6.3 11.56Z" id="icon-additional"/>
</symbol>
<symbol id="icon-measurements-lesions" viewBox="0 0 18 10">
<title>
Lesions
</title>
<path d="M0 6L2 6 2 4 0 4 0 6 0 6ZM0 10L2 10 2 8 0 8 0 10 0 10ZM0 2L2 2 2 0 0 0 0 2 0 2ZM4 6L18 6 18 4 4 4 4 6 4 6ZM4 10L18 10 18 8 4 8 4 10 4 10ZM4 0L4 2 18 2 18 0 4 0 4 0Z" id="icon-lesions"/>
</symbol>
<symbol id="icon-series-image-count" viewBox="0 0 13 13">
<title>
Image Count
</title>
<path d="M10.12 3.94L13 3.94 13 13 3.94 13 3.94 10.12 10.12 10.12 10.12 3.94Z" id="Combined-Shape"/>
<rect id="Rectangle-36" x="0" y="0" width="9.06" height="9.06"/>
</symbol>
<symbol id="icon-series-number" viewBox="0 0 9 9">
<title>
Series Number
</title>
<path d="M4.2 6.11C4.2 5.86 4.12 5.66 3.94 5.52 3.76 5.38 3.45 5.24 3 5.09 2.56 4.94 2.19 4.8 1.91 4.66 0.99 4.2 0.53 3.58 0.53 2.79 0.53 2.4 0.64 2.05 0.87 1.75 1.1 1.45 1.43 1.21 1.85 1.04 2.27 0.88 2.74 0.79 3.26 0.79 3.77 0.79 4.23 0.88 4.64 1.07 5.04 1.25 5.36 1.51 5.58 1.85 5.81 2.18 5.92 2.57 5.92 3L4.21 3C4.21 2.71 4.12 2.49 3.94 2.33 3.77 2.17 3.53 2.09 3.22 2.09 2.92 2.09 2.67 2.15 2.5 2.29 2.32 2.42 2.23 2.6 2.23 2.8 2.23 2.99 2.33 3.15 2.52 3.3 2.72 3.45 3.06 3.6 3.55 3.76 4.05 3.92 4.45 4.09 4.77 4.27 5.53 4.71 5.92 5.32 5.92 6.1 5.92 6.72 5.68 7.21 5.21 7.57 4.75 7.92 4.1 8.1 3.29 8.1 2.71 8.1 2.19 7.99 1.72 7.79 1.25 7.58 0.9 7.3 0.67 6.94 0.43 6.58 0.31 6.16 0.31 5.7L2.03 5.7C2.03 6.08 2.13 6.36 2.33 6.54 2.52 6.72 2.84 6.81 3.29 6.81 3.57 6.81 3.79 6.75 3.96 6.63 4.12 6.5 4.2 6.33 4.2 6.11L4.2 6.11ZM7.74 6.4C8 6.4 8.22 6.48 8.39 6.64 8.57 6.79 8.65 6.99 8.65 7.23 8.65 7.47 8.57 7.67 8.39 7.82 8.22 7.98 8 8.05 7.74 8.05 7.48 8.05 7.26 7.98 7.09 7.82 6.92 7.66 6.84 7.47 6.84 7.23 6.84 6.99 6.92 6.79 7.09 6.64 7.26 6.48 7.48 6.4 7.74 6.4L7.74 6.4ZM7.74 2.51C8 2.51 8.22 2.58 8.39 2.74 8.57 2.89 8.65 3.09 8.65 3.33 8.65 3.57 8.57 3.77 8.39 3.93 8.22 4.08 8 4.16 7.74 4.16 7.48 4.16 7.26 4.08 7.09 3.92 6.92 3.77 6.84 3.57 6.84 3.33 6.84 3.09 6.92 2.9 7.09 2.74 7.26 2.58 7.48 2.51 7.74 2.51L7.74 2.51Z" id="S:"/>
</symbol>
<symbol id="icon-settings" viewBox="0 0 15 15">
<title>
Settings
</title>
<path d="M12.85 8.25C12.85 8.03 12.93 7.8 12.93 7.5 12.93 7.2 12.93 6.98 12.85 6.75L14.43 5.48C14.58 5.33 14.58 5.18 14.5 5.03L13 2.4C12.93 2.33 12.78 2.25 12.55 2.33L10.68 3.08C10.3 2.78 9.85 2.55 9.4 2.33L9.1 0.38C9.18 0.15 8.95 0 8.8 0L5.8 0C5.65 0 5.43 0.15 5.43 0.3L5.13 2.33C4.68 2.48 4.3 2.78 3.85 3.08L2.05 2.33C1.83 2.25 1.68 2.33 1.53 2.48L0.03 5.1C-0.05 5.18 0.03 5.4 0.18 5.55L1.75 6.75C1.75 6.98 1.68 7.2 1.68 7.5 1.68 7.8 1.68 8.03 1.75 8.25L0.18 9.53C0.03 9.68 0.03 9.82 0.1 9.98L1.6 12.6C1.68 12.68 1.83 12.75 2.05 12.68L3.93 11.93C4.3 12.23 4.75 12.45 5.2 12.68L5.5 14.63C5.5 14.78 5.65 14.93 5.88 14.93L8.88 14.93C9.03 14.93 9.25 14.78 9.25 14.63L9.55 12.68C10 12.45 10.45 12.23 10.83 11.93L12.7 12.68C12.85 12.75 13.08 12.68 13.15 12.53L14.65 9.9C14.73 9.75 14.73 9.53 14.58 9.45L12.85 8.25 12.85 8.25ZM7.3 10.13C5.88 10.13 4.68 8.93 4.68 7.5 4.68 6.08 5.88 4.88 7.3 4.88 8.73 4.88 9.93 6.08 9.93 7.5 9.93 8.93 8.73 10.13 7.3 10.13L7.3 10.13Z" id="Shape"/>
</symbol>
<symbol id="icon-status-complete" viewBox="0 0 27 27">
<title>
Complete
</title>
<path d="M13.5 0C6.08 0 0 6.04 0 13.42 0 20.8 6.08 26.83 13.5 26.83 20.93 26.83 27 20.8 27 13.42 27 6.04 20.93 0 13.5 0L13.5 0ZM10.8 20.13L4.05 13.42 5.94 11.54 10.8 16.37 21.06 6.17 22.95 8.05 10.8 20.13 10.8 20.13Z" id="Shape"/>
</symbol>
<symbol id="icon-status-lock" viewBox="0 0 16 21">
<title>
Locked
</title>
<path d="M14 7L13 7 13 5C13 2.2 10.8 0 8 0 5.2 0 3 2.2 3 5L3 7 2 7C0.9 7 0 7.9 0 9L0 19C0 20.1 0.9 21 2 21L14 21C15.1 21 16 20.1 16 19L16 9C16 7.9 15.1 7 14 7L14 7ZM11.1 7L4.9 7 4.9 5C4.9 3.3 6.3 1.9 8 1.9 9.7 1.9 11.1 3.3 11.1 5L11.1 7 11.1 7Z"/>
</symbol>
<symbol id="icon-studies" viewBox="0 0 15 13">
<title>
Studies
</title>
<path d="M0 13L7 13 7 7 0 7 0 13 0 13ZM0 0L0 6 7 6 7 0 0 0ZM8 13L15 13 15 7 8 7 8 13ZM8 0L8 6 15 6 15 0 8 0Z" id="boxes" stroke="none"/>
</symbol>
<symbol id="icon-study-modality" viewBox="0 0 64 62">
<title>
Modality
</title>
<path d="M48.93 4C47.55 1.61 44.96 0 41.99 0L8.01 0C3.58 0 0 3.58 0 8.01L0 41.99C0 45.4 2.12 48.3 5.11 49.46 5.04 48.98 5 48.49 5 47.99L5 14.01C5 8.48 9.47 4 15.01 4L48.93 4Z" id="Combined-Shape"/>
<path d="M55.93 10C54.55 7.61 51.96 6 48.99 6L15.01 6C10.58 6 7 9.58 7 14.01L7 47.99C7 51.4 9.12 54.3 12.11 55.46 12.04 54.98 12 54.49 12 53.99L12 20.01C12 14.48 16.47 10 22.01 10L55.93 10Z" id="Combined-Shape"/>
<rect id="Rectangle-132" x="14" y="12" width="50" height="50" rx="8"/>
</symbol>
<symbol id="icon-tools-levels" viewBox="0 0 18 18">
<title>
Window / Level
</title>
<circle id="Oval-2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" cx="8" cy="8" r="8" fill="none"></circle>
<path d="M13.9867821,2.69341277 C15.2393779,4.10552346 16,5.96393532 16,8 C16,12.418278 12.418278,16 8,16 C5.96393532,16 4.10552346,15.2393779 2.69341277,13.9867821 L13.9867821,2.69341277 Z" id="Combined-Shape"></path>
</symbol>
<symbol id="icon-tools-link" viewBox="0 0 23 15">
<title>
Link
</title>
<circle id="Oval-4" opacity="0.8" cx="7.5" cy="7.5" r="7.5"/>
<circle id="Oval-4" opacity="0.6" cx="15.5" cy="7.5" r="7.5"/>
</symbol>
<symbol id="icon-tools-measure-non-target" viewBox="0 0 24 25">
<title>
Non-Target Measurement
</title>
<path d="M7.46 24.46L23.21 8.71C23.6 8.32 23.6 7.68 23.21 7.29 22.82 6.9 22.18 6.9 21.79 7.29L6.04 23.04C5.65 23.44 5.65 24.07 6.04 24.46 6.43 24.85 7.06 24.85 7.46 24.46L7.46 24.46Z" id="Line"/>
<path d="M7.75 23.75L7.75 17.74C7.75 17.19 7.3 16.74 6.75 16.74 6.2 16.74 5.75 17.19 5.75 17.74L5.75 23.75C5.75 24.3 6.2 24.75 6.75 24.75 7.3 24.75 7.75 24.3 7.75 23.75L7.75 23.75Z" id="Line"/>
<path d="M6.75 24.75L13 24.75C13.56 24.75 14 24.3 14 23.75 14 23.2 13.56 22.75 13 22.75L6.75 22.75C6.2 22.75 5.75 23.2 5.75 23.75 5.75 24.3 6.2 24.75 6.75 24.75L6.75 24.75Z" id="Line"/>
<g id="plus">
<path d="M6.5 12.5C9.81 12.5 12.5 9.81 12.5 6.5 12.5 3.19 9.81 0.5 6.5 0.5 3.19 0.5 0.5 3.19 0.5 6.5 0.5 9.81 3.19 12.5 6.5 12.5L6.5 12.5ZM6.5 11.5C3.74 11.5 1.5 9.26 1.5 6.5 1.5 3.74 3.74 1.5 6.5 1.5 9.26 1.5 11.5 3.74 11.5 6.5 11.5 9.26 9.26 11.5 6.5 11.5L6.5 11.5Z" id="Oval-6"/>
<path d="M6 7L3.5 7C3.22 7 3 6.78 3 6.5 3 6.22 3.22 6 3.5 6L6 6 6 3.5C6 3.22 6.22 3 6.5 3 6.78 3 7 3.22 7 3.5L7 6 9.58 6C9.86 6 10.08 6.22 10.08 6.5 10.08 6.78 9.86 7 9.58 7L7 7 7 9.58C7 9.86 6.78 10.08 6.5 10.08 6.22 10.08 6 9.86 6 9.58L6 7Z" id="Combined-Shape"/>
</g>
</symbol>
<symbol id="icon-tools-measure-target" viewBox="0 0 27 26">
<title>
Target Measurement
</title>
<path d="M14.25 20.74L15.71 19.28 14.34 17.91 15.05 17.21 16.42 18.57 17.79 17.2 15.31 14.73 16.02 14.02 18.5 16.5 19.96 15.04 18.59 13.67 19.29 12.96 20.66 14.33 22.03 12.96 19.56 10.49 20.26 9.78 22.74 12.25 24.2 10.8 22.83 9.43 23.54 8.72 24.91 10.09 26.99 8 23.07 4.08C22.29 3.3 21.02 3.3 20.24 4.08L5.39 18.92C4.61 19.71 4.61 20.97 5.39 21.76L9.32 25.68 11.47 23.52 10.1 22.16 10.81 21.45 12.18 22.82 13.55 21.45 11.07 18.97 11.78 18.26 14.25 20.74Z" id="Combined-Shape"/>
<path d="M5 6L2.5 6C2.22 6 2 5.78 2 5.5 2 5.22 2.22 5 2.5 5L5 5 5 2.5C5 2.22 5.22 2 5.5 2 5.78 2 6 2.22 6 2.5L6 5 8.58 5C8.86 5 9.08 5.22 9.08 5.5 9.08 5.78 8.86 6 8.58 6L6 6 6 8.58C6 8.86 5.78 9.08 5.5 9.08 5.22 9.08 5 8.86 5 8.58L5 6ZM5.5 11C8.54 11 11 8.54 11 5.5 11 2.46 8.54 0 5.5 0 2.46 0 0 2.46 0 5.5 0 8.54 2.46 11 5.5 11Z" id="Combined-Shape"/>
</symbol>
<symbol id="icon-tools-measure-temp" viewBox="0 0 26 28">
<title>
Temporary Measurement
</title>
<path d="M4.81 26.81L5.52 26.11C6.1 25.52 6.1 24.57 5.52 23.98 4.93 23.4 3.98 23.4 3.39 23.98L2.69 24.69C2.1 25.28 2.1 26.23 2.69 26.81 3.27 27.4 4.22 27.4 4.81 26.81L4.81 26.81ZM9.76 21.86L10.47 21.16C11.05 20.57 11.05 19.62 10.47 19.03 9.88 18.45 8.93 18.45 8.34 19.03L7.64 19.74C7.05 20.33 7.05 21.28 7.64 21.86 8.22 22.45 9.17 22.45 9.76 21.86L9.76 21.86ZM14.71 16.91L15.42 16.21C16 15.62 16 14.67 15.42 14.08 14.83 13.5 13.88 13.5 13.29 14.08L12.59 14.79C12 15.38 12 16.33 12.59 16.91 13.17 17.5 14.12 17.5 14.71 16.91L14.71 16.91ZM19.66 11.96L20.37 11.26C20.95 10.67 20.95 9.72 20.37 9.13 19.78 8.55 18.83 8.55 18.24 9.13L17.54 9.84C16.95 10.43 16.95 11.38 17.54 11.96 18.12 12.55 19.07 12.55 19.66 11.96L19.66 11.96ZM24.61 7.01L25.32 6.31C25.9 5.72 25.9 4.77 25.32 4.18 24.73 3.6 23.78 3.6 23.19 4.18L22.49 4.89C21.9 5.48 21.9 6.43 22.49 7.01 23.07 7.6 24.02 7.6 24.61 7.01L24.61 7.01Z" id="Line"/>
<g id="plus">
<path d="M6.5 12.5C9.81 12.5 12.5 9.81 12.5 6.5 12.5 3.19 9.81 0.5 6.5 0.5 3.19 0.5 0.5 3.19 0.5 6.5 0.5 9.81 3.19 12.5 6.5 12.5L6.5 12.5ZM6.5 11.5C3.74 11.5 1.5 9.26 1.5 6.5 1.5 3.74 3.74 1.5 6.5 1.5 9.26 1.5 11.5 3.74 11.5 6.5 11.5 9.26 9.26 11.5 6.5 11.5L6.5 11.5Z" id="Oval-6"/>
<path d="M6 7L3.5 7C3.22 7 3 6.78 3 6.5 3 6.22 3.22 6 3.5 6L6 6 6 3.5C6 3.22 6.22 3 6.5 3 6.78 3 7 3.22 7 3.5L7 6 9.58 6C9.86 6 10.08 6.22 10.08 6.5 10.08 6.78 9.86 7 9.58 7L7 7 7 9.58C7 9.86 6.78 10.08 6.5 10.08 6.22 10.08 6 9.86 6 9.58L6 7Z" id="Combined-Shape"/>
</g>
</symbol>
<symbol id="icon-tools-more" viewBox="0 0 18 18">
<title>
More
</title>
<path d="M9 18C13.97 18 18 13.97 18 9 18 4.03 13.97 0 9 0 4.03 0 0 4.03 0 9 0 13.97 4.03 18 9 18ZM4.5 8C3.68 8 3 8.68 3 9.5 3 10.33 3.68 11 4.5 11 5.33 11 6 10.33 6 9.5 6 8.68 5.33 8 4.5 8ZM13.5 8C12.68 8 12 8.68 12 9.5 12 10.33 12.68 11 13.5 11 14.33 11 15 10.33 15 9.5 15 8.68 14.33 8 13.5 8ZM9 8C8.18 8 7.5 8.68 7.5 9.5 7.5 10.33 8.18 11 9 11 9.82 11 10.5 10.33 10.5 9.5 10.5 8.68 9.82 8 9 8Z" id="Combined-Shape"/>
</symbol>
<symbol id="icon-tools-pan" viewBox="0 0 18 18">
<title>
Pan
</title>
<path d="M8,15 L8,0" id="Line" stroke-width="2px"></path>
<path d="M15,8 L1,8" id="Line" stroke-width="2px"></path>
<polyline stroke-width="2px" points="6 1.99816663 7.99816663 0 10.0091928 2.01102613"></polyline>
<polyline stroke-width="2px" transform="translate(8.004596, 15.005513) rotate(-180.000000) translate(-8.004596, -15.005513) " points="6 15.9981666 7.99816663 14 10.0091928 16.0110261"></polyline>
<polyline stroke-width="2px" transform="translate(1.005513, 8.004596) rotate(-90.000000) translate(-1.005513, -8.004596) " points="-0.999083316 8.99724995 0.999083316 6.99908332 3.01010945 9.01010945"></polyline>
<polyline stroke-width="2px" transform="translate(15.005513, 8.004596) rotate(-270.000000) translate(-15.005513, -8.004596) " points="13.0009167 8.99724995 14.9990833 6.99908332 17.0101094 9.01010945"></polyline>
</symbol>
<symbol id="icon-tools-zoom" viewBox="0 0 17 17">
<title>
Zoom
</title>
<path d="M10.5 10.5 L15 15"/>
<ellipse cx="6" cy="6" rx="6" ry="6" fill="none"/>
</symbol>
<symbol id="icon-trial-info" viewBox="0 0 20 20">
<title>
Trial Information
</title>
<path d="M9 15L11 15 11 9 9 9 9 15 9 15ZM10 0C4.5 0 0 4.5 0 10 0 15.5 4.5 20 10 20 15.5 20 20 15.5 20 10 20 4.5 15.5 0 10 0L10 0ZM10 18C5.6 18 2 14.4 2 10 2 5.6 5.6 2 10 2 14.4 2 18 5.6 18 10 18 14.4 14.4 18 10 18L10 18ZM9 7L11 7 11 5 9 5 9 7 9 7Z" id="Shape"/>
</symbol>
<symbol id="icon-ui-accordion-expand" viewBox="0 0 12 8">
<title>
Expand
</title>
<polygon transform="translate(6.000000, 3.700000) rotate(-270.000000) translate(-6.000000, -3.700000) " points="3.7 -2.3 2.3 -0.9 6.9 3.7 2.3 8.3 3.7 9.7 9.7 3.7"/>
</symbol>
<symbol id="icon-ui-add" viewBox="0 0 12 13">
<title>
Add
</title>
<path d="M6.5 7L6.5 12C6.5 12.28 6.28 12.5 6 12.5 5.72 12.5 5.5 12.28 5.5 12L5.5 7 0.5 7C0.22 7 0 6.78 0 6.5 0 6.22 0.22 6 0.5 6L5.5 6 5.5 1C5.5 0.72 5.72 0.5 6 0.5 6.28 0.5 6.5 0.72 6.5 1L6.5 6 11.5 6C11.78 6 12 6.22 12 6.5 12 6.78 11.78 7 11.5 7L6.5 7Z" id="Combined-Shape"/>
</symbol>
<symbol id="icon-ui-close" viewBox="0 0 14 14">
<title>
Close
</title>
<polygon id="Shape" points="14 1.4 12.6 0 7 5.6 1.4 0 0 1.4 5.6 7 0 12.6 1.4 14 7 8.4 12.6 14 14 12.6 8.4 7"/>
</symbol>
<symbol id="icon-create-comment" viewBox="0 0 37 34">
<title>
Comment
</title>
<path fill="#94a8b3" d="M22.32 14L35 14C36.1 14 37 14.9 37 16L37 28C37 29.1 36.1 30 35 30L21 30 17 34 17 20.39C19.45 18.97 21.36 16.71 22.32 14Z" id="Combined-Shape"/>
<path d="M10 0C4.5 0 0 4.5 0 10 0 15.5 4.5 20 10 20 15.5 20 20 15.5 20 10 20 4.5 15.5 0 10 0L10 0ZM15 11L11 11 11 15 9 15 9 11 5 11 5 9 9 9 9 5 11 5 11 9 15 9 15 11 15 11Z" id="Shape"/>
</symbol>
<symbol id="icon-create-screen-capture" viewBox="0 0 37 35">
<title>
Capture Screen
</title>
<g id="camera-alt" transform="translate(17.000000, 17.000000)" fill="#94a8b3">
<path d="M10 13C11.66 13 13 11.66 13 10 13 8.34 11.66 7 10 7 8.34 7 7 8.34 7 10 7 11.66 8.34 13 10 13Z" id="Oval"/>
<path d="M7 0L5.2 2 2 2C0.9 2 0 2.9 0 4L0 16C0 17.1 0.9 18 2 18L18 18C19.1 18 20 17.1 20 16L20 4C20 2.9 19.1 2 18 2L14.8 2 13 0 7 0 7 0ZM10 15C7.2 15 5 12.8 5 10 5 7.2 7.2 5 10 5 12.8 5 15 7.2 15 10 15 12.8 12.8 15 10 15L10 15Z" id="Shape"/>
</g>
<path d="M10 0C4.5 0 0 4.5 0 10 0 15.5 4.5 20 10 20 15.5 20 20 15.5 20 10 20 4.5 15.5 0 10 0L10 0ZM15 11L11 11 11 15 9 15 9 11 5 11 5 9 9 9 9 5 11 5 11 9 15 9 15 11 15 11Z" id="Shape"/>
</symbol>
<symbol id="icon-ui-warning" viewBox="0 0 22 20">
<title>
Warning
</title>
<path d="M0 20L22 20 11 0 0 20 0 20ZM12 16.84L10 16.84 10 14.74 12 14.74 12 16.84 12 16.84ZM12 12.63L10 12.63 10 8.42 12 8.42 12 12.63 12 12.63Z"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,77 @@
<template name="additionalMeasurements">
<div id="additionalMeasurements">
<div class="scrollArea">
<div class="section">
<div class="header">
Additional Findings: Baseline
</div>
<div>
{{ >radioOptionGroup measurableDiseaseButtonData}}
</div>
</div>
<div class="section">
<div class="header">
Supplementary Measurements
</div>
<div class="entrySection">
<label>Number of bone lesions</label>
<div>
<select>
<option>0</option>
<option>1-2</option>
<option selected>2-4</option>
<option>>5</option>
</select>
</div>
</div>
<div class="entrySection">
<label>Regions of metastatic disease</label>
<select id="regionsOfMetastaticDisease">
<option>None</option>
<option>Lymph node</option>
</select>
</div>
{{ >radioOptionGroup metastaticDiseaseButtonData}}
</div>
<div class="section">
<div class="header">
Image Quality
</div>
{{ >radioOptionGroup imageQualityButtonData}}
{{ >radioOptionGroup anatomicalCoverageButtonData}}
{{ >radioOptionGroup presenceOfContrastButtonData}}
</div>
<div class="section">
<div class="header">
Additional Screen Captures
</div>
<div class="entrySection buttonSection">
<div class="svgContainer">
<svg>
<use xlink:href="/packages/lesiontracker/assets/icons.svg#icon-create-screen-capture"></use>
</svg>
</div>
<div class="buttonLabel noselect">
Add new screen capture
</div>
</div>
</div>
<div class="section">
<div class="header">
Comments
</div>
<div class="entrySection buttonSection">
<div class="svgContainer">
<svg>
<use xlink:href="/packages/lesiontracker/assets/icons.svg#icon-create-comment"></use>
</svg>
</div>
<div class="buttonLabel noselect">
Add a comment
</div>
</div>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,87 @@
var yesNoOptions = [{
key: 'yes',
text: 'Yes'
}, {
key: 'no',
text: 'No'
}];
Template.additionalMeasurements.onCreated(function additionalMeasurementsOnCreated() {
const instance = this;
instance.measurableDisease = new ReactiveVar();
instance.numberOfBoneLesions = new ReactiveVar();
instance.regionsOfMetastaticDisease = new ReactiveVar();
instance.tracerRelatedToMetastaticDisease = new ReactiveVar();
instance.acceptableImageQuality = new ReactiveVar();
instance.adequateAnatomicalCoverage = new ReactiveVar();
instance.presenceOfContrast = new ReactiveVar();
instance.phaseOfContrast = new ReactiveVar();
instance.measurableDisease.set('absent');
instance.tracerRelatedToMetastaticDisease.set('no');
instance.acceptableImageQuality.set('yes');
instance.adequateAnatomicalCoverage.set('yes');
instance.presenceOfContrast.set('yes');
});
Template.additionalMeasurements.onRendered(function additionalMeasurementsOnRendered() {
const instance = this;
instance.$("#regionsOfMetastaticDisease").select2({
multiple: true
});
});
Template.additionalMeasurements.helpers({
measurableDiseaseButtonData() {
const instance = Template.instance();
return {
text: 'Measurable disease',
value: instance.measurableDisease,
options: [{
key: 'present',
text: 'Present'
}, {
key: 'absent',
text: 'Absent'
}]
};
},
metastaticDiseaseButtonData() {
const instance = Template.instance();
return {
text: 'Tracer related to metastatic disease',
value: instance.tracerRelatedToMetastaticDisease,
options: yesNoOptions
};
},
imageQualityButtonData() {
const instance = Template.instance();
return {
text: 'Acceptable image quality',
value: instance.acceptableImageQuality,
options: yesNoOptions
};
},
anatomicalCoverageButtonData() {
const instance = Template.instance();
return {
text: 'Adequate anatomical coverage',
value: instance.adequateAnatomicalCoverage,
options: yesNoOptions
};
},
presenceOfContrastButtonData() {
const instance = Template.instance();
return {
text: 'Presence of contrast',
value: instance.presenceOfContrast,
options: yesNoOptions
};
}
});

View File

@ -0,0 +1,74 @@
@import "{design}/app"
#additionalMeasurements
width: 100%
height: 100%
background-color: #000000
.scrollArea
height: 100%
overflow-x: hidden
overflow-y: auto
margin-right: -16px
padding-bottom: 100px
padding-right: 16px
-webkit-overflow-scrolling: touch
&::-webkit-scrollbar
display: none
.header
height: 33px
line-height: 33px
font-weight: 400
font-size: 14px
padding: 0 10px
color: $textPrimaryColor
background-color: $darkUiColor
.entrySection
#regionsOfMetastaticDisease
width: 285px
height: 30px
border-radius: 2px
background-color: #b6b6b6
color: #3e3e3e
font-size: 14px
.buttonSection
cursor: pointer
height: 100px
color: $defaultColor
&:hover
color: $hoverColor
.svgContainer
svg
fill: $hoverColor
&:active
color: $activeColor
.svgContainer
svg
fill: $activeColor
div
float: left
display: inline-block
height: 80px
.buttonLabel
line-height: 80px
padding: 0 30px
.svgContainer
padding: 15px 5px
width: 40px
svg
width: 40px
height: 40px
fill: $textPrimaryColor

View File

@ -0,0 +1,16 @@
<template name="radioOptionGroup">
<div class="{{class}} entrySection viewerRadioOptionGroup">
<h5>{{text}}</h5>
<div>
{{#each item in this.options}}
<label>
<input type="radio"
name="{{item.key}}"
data-key="{{item.key}}"
{{inlineIf value.get item.key 'selected'}}/>
{{item.text}}
</label>
{{/each}}
</div>
</div>
</template>

View File

@ -0,0 +1,15 @@
Template.radioOptionGroup.onRendered(() => {
const instance = Template.instance();
const value = instance.data.value;
if (!value.get()) {
value.set(instance.data.options[0].key);
}
});
Template.radioOptionGroup.events({
'click [data-key]'(event, instance) {
const $element = $(event.currentTarget);
const key = $element.attr('data-key');
instance.data.value.set(key);
}
});

View File

@ -0,0 +1,20 @@
@import "{design}/app"
.entrySection
padding: 10px
color: $textPrimaryColor
&:not(:last-child)
border-bottom: $uiBorderThickness solid $uiBorderColorDark
h5
font-weight: 300
label
font-size: 14px
font-weight: 100
display: inline-block
padding: 5px
input
margin: 5px

View File

@ -0,0 +1,11 @@
<template name="caseProgress">
<div class="caseProgress">
{{ >radialProgressBar isLocked=isLocked progressPercent=progressPercent progressText=progressText}}
{{ #if isLocked }}
<h5>Locked</h5>
{{ /if }}
{{ #if progressComplete }}
<button class="btn">Enter response</button>
{{ /if }}
</div>
</template>

View File

@ -0,0 +1,30 @@
Template.caseProgress.onCreated(function caseProgressOnCreated() {
const instance = Template.instance();
instance.progressPercent = new ReactiveVar();
instance.progressPercent.set(50);
instance.progressText = new ReactiveVar();
instance.progressText.set(5);
instance.isLocked = new ReactiveVar(false);
});
Template.caseProgress.helpers({
progressPercent() {
return Template.instance().progressPercent.get();
},
progressText() {
return Template.instance().progressText.get();
},
isLocked() {
return Template.instance().isLocked.get();
},
progressComplete() {
var progressPercent = Template.instance().progressPercent.get();
return progressPercent === 100;
}
});

View File

@ -0,0 +1,3 @@
@import "{design}/app"
//.caseProgress

View File

@ -0,0 +1,20 @@
<template name="flexboxLayout">
<div class="viewerSection">
<div class="sidebarMenu sidebar-left {{ #if studySidebarOpen }}sidebar-open{{/if}}">
<div class="timepointButtonContainer p-t-2">
{{>roundedButtonGroup buttonGroupData}}
</div>
<hr class="m-y-0">
{{>studyTimepointBrowser timepointViewType=timepointViewType}}
</div>
<div class="mainContent {{ #if studySidebarOpen }}sidebar-left-open{{/if}} {{ #if rightSidebarOpen }}sidebar-right-open{{/if}}">
{{>viewerMain }}
</div>
<div class="sidebarMenu sidebar-right {{ #if lesionSidebarOpen }}sidebar-open{{/if}}">
{{>lesionTable }}
</div>
<div class="sidebarMenu sidebar-right {{ #if additionalMeasurementsSidebarOpen }}sidebar-open{{/if}}">
{{>additionalMeasurements }}
</div>
</div>
</template>

View File

@ -0,0 +1,66 @@
Template.flexboxLayout.onCreated(() => {
const instance = Template.instance();
instance.state = instance.data.state;
instance.timepointViewType = new ReactiveVar();
});
var resizeTimeout;
Template.flexboxLayout.onRendered(() => {
const instance = Template.instance();
instance.autorun(function() {
Meteor.clearTimeout(resizeTimeout);
// Trigger a resize any time the layout state changes
var studySidebarOpen = instance.state.get('studySidebarOpen');
var lesionSidebarOpen = instance.state.get('lesionSidebarOpen');
var additionalMeasurementsSidebarOpen = instance.state.get('additionalMeasurementsSidebarOpen');
resizeTimeout = Meteor.setTimeout(function() {
handleResize();
}, 300);
});
});
Template.flexboxLayout.helpers({
timepointViewType() {
return Template.instance().timepointViewType;
},
buttonGroupData() {
const instance = Template.instance();
return {
value: instance.timepointViewType,
options: [{
key: 'key',
text: 'Key Timepoints'
}, {
key: 'all',
text: 'All Timepoints'
}]
};
},
studySidebarOpen() {
const instance = Template.instance();
return instance.state.get('studySidebarOpen');
},
lesionSidebarOpen() {
const instance = Template.instance();
return instance.state.get('lesionSidebarOpen');
},
additionalMeasurementsSidebarOpen() {
const instance = Template.instance();
return instance.state.get('additionalMeasurementsSidebarOpen');
},
rightSidebarOpen() {
const instance = Template.instance();
const lesionSidebarOpen = instance.data.state.get('lesionSidebarOpen');
const additionalMeasurementsSidebarOpen = instance.data.state.get('additionalMeasurementsSidebarOpen');
return lesionSidebarOpen || additionalMeasurementsSidebarOpen;
}
});

View File

@ -0,0 +1,69 @@
@import "{design}/app"
.viewerSection
display: inline-flex
flex-flow: row nowrap
align-items: stretch
height: 'calc(100% - %s)' % $toolbarHeight
width: 100%
.sidebarMenu
position: absolute
height: 100%
background: $primaryBackgroundColor
-webkit-transition: $sidebarTransition
-ms-transition: $sidebarTransition
-o-transition: $sidebarTransition
-moz-transition: $sidebarTransition
transition: $sidebarTransition
.timepointButtonContainer
height: 55px
.sidebar-left
width: $studiesSidebarMenuWidth
order: 1
flex: 0
left: - $studiesSidebarMenuWidth
border-right: $uiBorderThickness $uiBorderColor solid
&.sidebar-open
left: 0
.mainContent
order: 2
width: 100%
height: 100%
-webkit-transition: $sidebarTransition
-ms-transition: $sidebarTransition
-o-transition: $sidebarTransition
-moz-transition: $sidebarTransition
transition: $sidebarTransition
transform: translate3d(0,0,0);
&.sidebar-left-open.sidebar-right-open
width: 'calc(100% - %s)' % ($studiesSidebarMenuWidth + $lesionsSidebarMenuWidth)
&.sidebar-right-open
width: 'calc(100% - %s)' % $lesionsSidebarMenuWidth
&.sidebar-left-open
width: 'calc(100% - %s)' % $studiesSidebarMenuWidth
&.sidebar-left-open
transform: translate3d($studiesSidebarMenuWidth,0,0);
.sidebar-right
width: $lesionsSidebarMenuWidth
order: 3
flex: 0
right: - $lesionsSidebarMenuWidth
border-left: $uiBorderThickness $uiBorderColor solid
&.sidebar-open
right: 0
.studiesListedChanger
padding: 20px 10px
text-align: center
border-bottom: $uiBorderThickness $uiBorderColor solid

View File

@ -9,9 +9,7 @@
</button>
</div>
<div class="hidingPanelContent">
{{#if studyDateIsShown}}
{{> studyDateList}}
{{/if}}
{{> studyDateList}}
<div class="studyBrowserContainer">
{{> studyBrowser}}
</div>

View File

@ -1,6 +1,6 @@
function pinPanel() {
var hidingPanel = $(".hidingPanel");
var hidingPanel = $('.hidingPanel');
hidingPanel.css({
width: '122px'
});
@ -12,12 +12,12 @@ function pinPanel() {
// Calculate newWidth of viewportAndLesionTable
var viewerWidth = $('#viewer').width();
var newWidth = 100 - 122 / viewerWidth * 100;
$("#viewportAndLesionTable").css('width', newWidth + '%');
$('#viewportAndLesionTable').css('width', newWidth + '%');
}
function unpinPanel() {
// Calculate newWidth of viewportAndLesionTable
$("#viewportAndLesionTable").css('width', '99%');
// Calculate newWidth of viewportAndLesionTable
$('#viewportAndLesionTable').css('width', '99%');
}
Template.hidingPanel.events({
@ -90,9 +90,6 @@ Template.hidingPanel.events({
});
Template.hidingPanel.helpers({
studyDateIsShown: function() {
return true;
},
isPinned: function() {
return Template.instance().isPinned.get();
}
@ -117,7 +114,7 @@ Template.hidingPanel.onCreated(function() {
if (!isPanelPinned) {
isPanelPinned = false;
}
this.isPinned = new ReactiveVar(isPanelPinned);
});

View File

@ -1,27 +1,30 @@
<template name="lesionTable">
<div id="lesionTableContainer">
<div id="dragbar"></div>
<div id="lesionTableWrapper">
<table class="table table-striped noselect lesionTable" id="tblLesion">
<thead>
<tr>
<th id="thNumber">Lesion #</th>
<!--<th id="thLocation">Location</th>-->
<th id="thTarget">Target</th>
<!--Each time point name as a column-->
{{ #each timepoints }}
{{ >lesionTableTimepointHeader }}
{{ /each }}
</tr>
</thead>
<tbody>
<!--Each measurement as row-->
{{ #each measurement }}
{{ >lesionTableRow }}
{{ /each }}
</tbody>
</table>
<div class="lesionTableLayoutChanger viewerRoundedButtonGroup">
{{ >roundedButtonGroup buttonGroupData }}
</div>
<div class="lesionTableTimepointHeaderRow">
<div class="spacer">
</div>
{{ #each timepoints }}
{{ >lesionTableTimepointHeader }}
{{ /each }}
</div>
<div class="lesionTableWrapper">
{{ >lesionTableHeaderRow id="target" type="Targets" measurements=targets}}
{{ #each targets }}
{{ >lesionTableRow }}
{{ /each }}
{{ >lesionTableHeaderRow id="nonTarget" type="Non-targets" measurements=nonTargets}}
{{ #each nonTargets }}
{{ >lesionTableRow }}
{{ /each }}
{{ >lesionTableHeaderRow id="newLesions" type="New lesions" measurements=newLesions}}
{{ #each nonTargets }}
{{ >lesionTableRow }}
{{ /each }}
</div>
</div>
</template>

View File

@ -1,9 +1,30 @@
Template.lesionTable.helpers({
measurement: function() {
targets: function() {
// All Targets shall be listed first followed by Non-Targets
return Measurements.find({}, {
return Measurements.find({
isTarget: true
}, {
sort: {
lesionNumberAbsolute: 1
}
});
},
nonTargets: function() {
// All Targets shall be listed first followed by Non-Targets
return Measurements.find({
isTarget: false
}, {
sort: {
lesionNumberAbsolute: 1
}
});
},
newLesions: function() {
// All Targets shall be listed first followed by Non-Targets
return Measurements.find({
saved: false
}, {
sort: {
isTarget: -1,
lesionNumberAbsolute: 1
}
});
@ -14,59 +35,43 @@ Template.lesionTable.helpers({
latestDate: 1
}
});
},
buttonGroupData() {
const instance = Template.instance();
return {
value: instance.lesionTableLayout,
options: [{
key: 'comparison',
text: 'Comparison'
}, {
key: 'key',
text: 'Key Timepoints'
}]
};
}
});
Template.lesionTable.events({
// 'click table#tblLesion tbody tr': function(e, template) {
// Retrieve the lesion id from the DOM data for this row
// var measurementId = $(e.currentTarget).data('measurementid');
// activateLesion(measurementId, template.data);
//},
'mousedown div#dragbar': function(e, template) {
var pY = e.pageY;
var draggableParent = $(e.currentTarget).parent();
var startHeight = draggableParent.height();
template.dragging.set(true);
$(document).on('mouseup', function(e) {
template.dragging.set(false);
$(document).off('mouseup').off('mousemove');
});
$(document).on('mousemove', function(e) {
var topPosition = e.pageY - pY;
var newHeight = startHeight - topPosition;
// Min lesion table height = 5px
if (newHeight < 5) {
return;
}
draggableParent.css({
top: topPosition,
height: newHeight
});
var viewportAndLesionTableHeight = $('#viewportAndLesionTable').height();
var newPercentageHeightofLesionTable = (startHeight - topPosition) / viewportAndLesionTableHeight * 100;
var newPercentageHeightofViewermain = 100 - newPercentageHeightofLesionTable;
$('.viewerMain').height(newPercentageHeightofViewermain + '%');
// Resize viewport
resizeViewportElements();
});
}
});
Template.lesionTable.onCreated(function() {
this.dragging = new ReactiveVar(false);
/**
* Retrieve the lesion id from the DOM data for this row
*/
/*'click table#tblLesion tbody tr': function(e, template) {
var measurementId = $(e.currentTarget).data('measurementid');
activateLesion(measurementId, template.data);
},*/
});
// Temporary until we have a real window manager with events for series/study changed
Session.setDefault('NewSeriesLoaded', false);
Template.lesionTable.onCreated(function() {
var instance = this;
instance.lesionTableLayout = new ReactiveVar();
instance.lesionTableLayout.set('comparisonTimepoints');
});
Template.lesionTable.onRendered(function() {
// Find the first measurement by Lesion Number
var firstLesion = Measurements.findOne({}, {

View File

@ -1,93 +1,23 @@
@import "{design}/app"
#lesionTableContainer
width: 100%
height: 16%
background-color: #000000
#dragbar
width: 100%
height: 3px
top: 0
left: 0
right: 0
bottom: 0
background-color: #666666
cursor: row-resize
#lesionTableWrapper
position: relative
width: 100%
height: 100%
margin: 0 auto
.lesionTable
.lesionTableTimepointHeaderRow
display: flex
td, th
border: 1px solid #FFFFFF
border-collapse: collapse
width: 100px
.spacer
width: 20px
td.lesionNumber, th#thNumber
width: 70px
td.location, th#thLocation
width: 250px
td.target, th#thTarget
width: 70px
thead
width: 100%
height: 40px
display: block
tr
width: 100%
height: 40px
th
border-bottom-width: 0
font-weight: normal
color: #FFFFFF
line-height: 15px
text-align: center
margin: 0
padding: 5px
background-color: #424242
vertical-align: middle
tbody
display: block
position: absolute
top: 40px
left: 0
right: 0
bottom: 0
overflow: auto
width: 100%
padding-bottom: 20px
tr.selectedRow
background: #F5F5F5 !important
color: #000000 !important
tr.lesionTableRow
width: 100%
&:nth-child(even)
background: #888
&:nth-child(odd)
background: #BBB
&:hover
cursor: pointer
background-color: #009BD2
color: white
td
border-bottom-width: 0
margin: 0
padding: 5px
line-height: 15px
&.lesionNumber, &.target, &.lesionTableTimepointCell
text-align: center
.lesionTableLayoutChanger
padding: 5px
text-align: center
margin: 0 auto

View File

@ -0,0 +1,22 @@
<template name="lesionTableHeaderRow">
<div class="lesionTableHeaderRow">
<div class="add">
<svg>
<use xlink:href=/packages/lesiontracker/assets/icons.svg#icon-ui-add></use>
</svg>
</div>
<div class="type">
{{type}}
</div>
<div class="max">
{{ #if maxNumLesions }}
<p class="maxNumLesions">
Max {{maxNumLesions}}
</p>
{{/if}}
</div>
<div class="numberOfLesions">
{{numberOfLesions}}
</div>
</div>
</template>

View File

@ -0,0 +1,38 @@
Template.lesionTableHeaderRow.helpers({
numberOfLesions: function() {
var instance = Template.instance();
return instance.data.measurements.count();
},
maxNumLesions: function() {
var instance = Template.instance();
var lesionType = instance.id;
// TODO: Check what we are annotating
var timepointType = 'baseline';
// Identify which Trial Conformance Criteria are currently being used
// Note that there may be more than one.
var criteriaTypes = TrialCriteriaTypes.find({
selected: true
}).map(function(criteria) {
return criteria.id;
});
var currentConstraints = getTrialCriteriaConstraints(criteriaTypes);
if (!currentConstraints) {
// For testing
return 10;
}
var criteria = currentConstraints[timepointType][lesionType];
return criteria.group.totalNumberOfLesions;
}
});
Template.lesionTableHeaderRow.events({
'click .add': function() {
console.log('Add was clicked');
// TODO: Set active tool to new type
}
});

View File

@ -0,0 +1,58 @@
@import "{design}/app"
$headerRowHeight = 63px
.lesionTableHeaderRow
background: $darkUiColor
color: $textSecondaryColor
fill: $textSecondaryColor
width: 100%
height: $headerRowHeight
display: flex
line-height: $headerRowHeight
div
flex: 1
justify-content: space-around
align-items: stretch
text-align: center
.add
padding: 0 10px
max-width: 20px
cursor: pointer
svg
height: $headerRowHeight
max-width: 11px
.type
cursor: pointer
font-weight: 200
height: $headerRowHeight
font-size: 13pt
.numberOfLesions
float: right
font-weight: 100
font-size: 40px
max-width: 30px
margin-right: 5px
height: $headerRowHeight
line-height: $headerRowHeight
.max
height: $headerRowHeight
background: $darkUiColor
.maxNumLesions
margin: 21px auto
height: 20px
line-height: 20px
width: 47px
font-size: 12px
text-transform: uppercase
font-weight: 500
border-radius: 3px
color: black
background-color: $textSecondaryColor;

View File

@ -1,11 +1,38 @@
<template name="lesionTableRow">
<tr id="{{lesionNumber}}" class="lesionTableRow" data-measurementid="{{_id}}">
<td class='lesionNumber'>{{lesionNumberAbsolute}}</td>
<!--<td class='location' tabindex="0">{{location}}</td>-->
<td class='target'>{{#if isTarget}} Y {{else}} N {{/if}}</td>
<!--Each time point as a column-->
{{# each timepoints }}
{{> lesionTableTimepointCell}}
{{/ each }}
</tr>
<div class="lesionTableRow" data-measurementid="{{_id}}">
<div class='lesionRowSidebar'>
<div class="lesionNumber">
{{lesionNumberAbsolute}}
</div>
<div class="lesionNumber">
{{ #if descriptionRequired }}
<div>
<svg>
<use xlink:href=/packages/lesiontracker/assets/icons.svg#icon-ui-warning></use>
</svg>
</div>
{{ /if }}
{{ #if responseStatus }}
<div>
CR
</div>
{{ /if }}
</div>
</div>
<div class="lesionDetails">
<div class='location'>
{{ #if location }}
{{location}}
{{ else }}
(No description)
{{ /if }}
</div>
<div class="timepointData">
<!--Each time point as a column-->
{{# each timepoints }}
{{> lesionTableTimepointCell}}
{{/ each }}
</div>
</div>
</div>
</template>

View File

@ -1,3 +1,39 @@
.lesionTableRow .location
&:hover
font-weight: bold
@import "{design}/app"
.lesionTableRow
display: flex
width: 100%
.lesionRowSidebar
flex: 1
color: $textSecondaryColor
background: $darkUiColor
max-width: 20px
height: 60px
.lesionNumber
padding: 3px
&.active
border-left: 4px $activeColor solid
.lesionDetails
flex: 1
display: flex
flex-direction: column
align-items: stretch
.location
flex: 1
text-align: center
color: $textSecondaryColor
height: 20px
width: 100%
.timepointData
display: flex
div
flex: 1
text-align: center
justify-content: space-around

View File

@ -1,11 +1,11 @@
<template name="lesionTableTimepointCell">
{{ #if hasDataAtThisTimepoint }}
{{ #if isBirectional }}
<td class="lesionTableTimepointCell targetCell" tabindex="1">{{displayData}}</td>
{{ else }}
<td class="lesionTableTimepointCell nonTargetCell" tabindex="1">{{displayData}}</td>
{{ /if }}
<div class="lesionTableTimepointCell" tabindex="1">
{{displayData}}
</div>
{{ else }}
<td class="lesionTableTimepointCell empty"></td>
<div class="lesionTableTimepointCell empty">
...
</div>
{{ /if }}
</template>

View File

@ -1,8 +1,6 @@
@import "{design}/app"
.lesionTableTimepointCell
&:hover
font-weight: bold
.nonTargetCell
&:hover
color: red
border-left: $uiBorderThickness $uiBorderColor solid
color: $textPrimaryColor
padding: 0 10px

View File

@ -1,6 +1,10 @@
<template name="lesionTableTimepointHeader">
<th class="lesionTableTimepointCell {{ #if isLoaded }}loaded{{/if}}" title="Latest study date: {{formatDA latestDate "MM/DD/YYYY"}}"
data-toggle="tooltip" data-placement="top">
{{ timepointName }}
</th>
<div class="lesionTableTimepointHeader">
<div class="timepointName">
{{ timepointName }}
</div>
<div class="timepointDate">
{{ formatDA latestDate "DD-MMM-YY"}}
</div>
</div>
</template>

View File

@ -1,3 +1,15 @@
#lesionTableContainer .lesionTable thead tr th.lesionTableTimepointCell.loaded
//font-weight: bold
color: #4fbfff
@import "{design}/app"
.lesionTableTimepointHeader
flex: 1
justify-content: space-around
div
text-align: center
.timepointName
color: $textSecondaryColor
.timepointDate
color: $textPrimaryColor
border-left: $uiBorderColor $uiBorderThickness solid

View File

@ -0,0 +1,35 @@
<template name="lesionTracker">
<div class="topBar">
<a class="brandSection pull-left" href="http://ohif.org">
<img class="logoImage" src="/images/logo.png">
<div class="logoText">
Open Health Imaging Foundation
</div>
</a>
{{ #unless onStudyList}}
<div class="studyListLinkSection pull-left">
<a id="loadStudyList">Study List</a>
</div>
{{ /unless }}
<div class="userAccountSection pull-right">
{{>userAccountMenu}}
</div>
</div>
{{>timeoutCountdownDialog}}
<div id="worklistTabs" class="tab-content">
<div class="tab-pane active" id="worklistTab">
<div class="worklistContainer">
{{> worklistResult }}
</div>
</div>
<div class="tab-pane" id="viewerTab">
</div>
</div>
{{ >associationModal }}
{{ >optionsModal }}
{{ >serverInformationModal }}
{{ >confirmRemoveTimepointAssociation }}
{{ >lastLoginModal }}
{{ >studyContextMenu }}
{{ >progressDialog}}
</template>

View File

@ -0,0 +1,65 @@
// Define the ViewerData global object
// If there is currently any Session data for this object,
// use this to repopulate the variable
ViewerData = Session.get('ViewerData') || {};
Template.lesionTracker.onCreated(function() {
// showViewer to go to viewer from audit
this.showWorklistMenu = new ReactiveVar(true);
// Get url and check worklist
var currentRoute = Router.current();
if (!currentRoute || !currentRoute.route) {
return;
}
var currentPath = currentRoute.route.path(this);
if (currentPath === '/' || currentPath === '/worklist') {
this.showWorklistMenu.set(false);
}
});
Template.lesionTracker.onRendered(function() {
var templateData = Template.currentData();
if (templateData && templateData.studyInstanceUid) {
var studyInstanceUid = templateData.studyInstanceUid;
openNewTab(studyInstanceUid, studyInstanceUid);
} else {
// If there is a tab set as active in the Session,
// switch to that now.
var contentId = Session.get('activeContentId');
// TODO: FIx this it seems to be forcing two switches
switchToTab(contentId);
}
Meteor.subscribe('hangingprotocols');
});
Template.lesionTracker.events({
'click #tablist a[data-toggle="tab"]': function(e) {
// If this tab is already active, do nothing
var tabButton = $(e.currentTarget);
var tabTitle = tabButton.parents('.tabTitle');
if (tabTitle.hasClass('active')) {
return;
}
// Otherwise, switch to the tab
var contentId = tabButton.data('target').replace('#', '');
switchToTab(contentId);
},
'click #loadStudyList': function() {
switchToTab('worklistTab');
}
});
Session.set('defaultSignInMessage', 'Tumor tracking in your browser.');
Template.lesionTracker.helpers({
showWorklistMenu: function() {
return Template.instance().showWorklistMenu.get();
},
onStudyList: function() {
return (Session.get('activeContentId') === 'worklistTab');
}
});

View File

@ -0,0 +1,47 @@
@import "{design}/app"
.topBar
height: $topBarHeight
color: $textPrimaryColor
background-color: $primaryBackgroundColor
.brandSection
height: 100%
display: inline-block
img.logoImage
height: 100%
margin: 0 5px
.logoText
display: inline-block
font-family: $logoFontFamily
font-weight: $logoFontWeight
color: $textPrimaryColor
.studyListLinkSection
border-left: $uiBorderThickness solid $uiBorderColor;
padding: 5px
margin: 0 10px
a
text-decoration: none
cursor: pointer
line-height: 100%
.userAccountSection
margin: 0 10px
#worklistTabs
height: "calc(100% - %s)" % $topBarHeight
// Override Viewerbase's borders
#imageViewerViewports .viewportContainer
border: none !important
outline: 0 !important // Prevent blue outline in Chrome
&:hover
&.active
&:hover.active
border: none !important
outline: 0 !important // Prevent blue outline in Chrome

View File

@ -1,12 +1,3 @@
<template name="lesionTrackerLayout">
<div class="logoContainer">
{{>userAccountMenu}}
<a class="navbar-brand" href="http://ohif.org">
<img src="/images/logo.png">
<h4 class="name">Open Health Imaging Foundation</h4>
</a>
</div>
{{>timeoutCountdownDialog}}
{{>progressDialog}}
{{>yield }}
</template>

View File

@ -1,126 +0,0 @@
Session.set('defaultSignInMessage', 'Tumor tracking in your browser.');
Template.lesionTrackerLayout.helpers({
fullName: function() {
return Meteor.user().profile.fullName;
},
showWorklistMenu: function() {
return Template.instance().showWorklistMenu.get();
},
currentUser: function() {
var verifyEmail = Meteor.settings && Meteor.settings.public && Meteor.settings.public.verifyEmail || false;
if (!Meteor.user() || !Meteor.userId()) {
return false;
}
if (!verifyEmail) {
return true;
}
if (Meteor.user().emails[0].verified) {
return true;
}
return false;
}
});
Template.lesionTrackerLayout.onCreated(function() {
// showViewer to go to viewer from audit
this.showWorklistMenu = new ReactiveVar(true);
// Get url and check worklist
var currentRoute = Router.current();
if (!currentRoute || !currentRoute.route) {
return;
}
var currentPath = currentRoute.route.path(this);
if (currentPath === '/' || currentPath === '/worklist') {
this.showWorklistMenu.set(false);
}
// Show countdown dialog
var handle;
$(document).on('TriggerOpenTimeoutCountdownDialog', function(e, leftTime) {
// Show modal dialog
handle = setInterval(function() {
leftTime--;
// Set countdownDialogLeftTime session
Session.set('countdownDialogLeftTime', leftTime);
// Show dialog
var dialog = $('#timeoutCountdownDialog');
if (dialog.css('display') === 'none') {
dialog.css('display', 'block');
}
}, 1000);
});
$(document).on('TriggerCloseTimeoutCountdownDialog', function(e) {
var dialog = $('#timeoutCountdownDialog');
dialog.css('display', 'none');
if (handle) {
clearInterval(handle);
// Close the dialog
dialog.css('display', 'none');
// Remove reviewers info for the user
Meteor.call('removeUserFromReviewers', Meteor.userId());
}
});
});
Template.lesionTrackerLayout.onRendered(function() {
var oldUserId = undefined;
var userName;
var lastLoginModalInterval;
Tracker.autorun(function() {
// Hook login/logout
var newUserId = Meteor.userId();
if (oldUserId === null && newUserId) {
Session.set('showLastLoginModal', true);
userName = Meteor.user().profile.fullName;
// Log
HipaaLogger.logEvent({
eventType: 'init',
userId: Meteor.userId(),
userName: userName
});
} else if (newUserId === null && oldUserId) {
// Set showLastLoginModal as null
Session.set('showLastLoginModal', null);
// Destroy interval for last login modal
Meteor.clearInterval(lastLoginModalInterval);
console.log('The user logged out');
// Log
// TODO: eventype is not defined for logout in hipaa-audit-log
/*HipaaLogger.logEvent({
eventType: 'logout',
userId: oldUserId,
userName: userName
});*/
// Remove the user from Reviewers
Meteor.call('removeUserFromReviewers', oldUserId);
}
oldUserId = Meteor.userId();
// Trigger last login date popup
if (Session.get('showLastLoginModal')) {
Modal.show('lastLoginModal');
lastLoginModalInterval = Meteor.setInterval( function() {
Modal.hide('lastLoginModal');
Session.set("showLastLoginModal", null);
}, 3000);
return true;
}
});
});

View File

@ -1,38 +0,0 @@
.logoContainer
position: absolute
height: 40px
top: 0
right: 0
padding: 0 10px
color: white
@media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait)
width: 15%
.navbar-brand
display: inline-block
padding: 0
margin-right: 5px
height: 100%
img
height: 30px
float: left
margin-right: 10px
margin-top: 5px
/* Media queries for iPhone6*/
@media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait)
height: 20px
margin-top: 10px
margin-bottom: 10px
margin-right: 5px
h4.name
float: left
margin: 10px 0
font-family: "Sanchez"
color: white
@media only screen and (min-device-width : 375px) and (max-device-width : 667px) and (orientation : portrait)
font-size: 0.3em
font-size: 0.3em
width: 50%

View File

@ -1,3 +1,102 @@
@import "{design}/app"
$viewportTagPadding = 20px
$imageSliderBorderRadius = 57px
$imageSliderColor = #22374d
$imageSliderTrackColor = rgba(0,0,0,0)
$imageSliderBorder = none
// Note that these are backwards due to the magic needed to make a cross-browser vertical slider
$imageSliderWidth = 12px
$imageSliderHeight = 39px
$imageSliderCursor = grab
.imageViewerViewportOverlay
.timepointName
color: #32BFFF
color: $textSecondaryColor
.dicomTag
font-weight: 200
.topleft
top: $viewportTagPadding
left: $viewportTagPadding
.topright
top: $viewportTagPadding
right: $viewportTagPadding
text-align: right
.bottomleft
bottom: $viewportTagPadding
left: $viewportTagPadding
.bottomright
bottom: $viewportTagPadding
right: $viewportTagPadding
text-align: right
.imageControls
#scrollbar
#imageSlider
-webkit-appearance: none
background-color: $imageSliderTrackColor
// --- Style the range track --- //
&::-webkit-slider-runnable-track
height: 5px
border: none
cursor: pointer
background-color: $imageSliderTrackColor
z-index: 6
&::-moz-range-track
height: 2px
border: none
cursor: pointer
background-color: $imageSliderTrackColor
z-index: 6
&::-ms-track
width: 100%
height: 2px
border: none
cursor: pointer
animate: 0.2s
background: transparent
border-width: 15px 0
color: $imageSliderTrackColor
// Hide any fill IE tries to add
&::-ms-fill-lower
background: $imageSliderTrackColor
&::-ms-fill-upper
background: $imageSliderTrackColor
// --- Style the range thumb --- //
&::-webkit-slider-thumb
-webkit-appearance: none !important
background-color: $imageSliderColor
border-radius: $imageSliderBorderRadius
border: $imageSliderBorder
height: $imageSliderWidth
width: $imageSliderHeight
cursor: $imageSliderCursor
margin-top: -7px
&::-moz-range-thumb
background-color: $imageSliderColor
border-radius: $imageSliderBorderRadius
border: $imageSliderBorder
height: $imageSliderWidth
width: $imageSliderHeight
cursor: $imageSliderCursor
z-index: 7
&::-ms-thumb
background-color: $imageSliderColor
border-radius: $imageSliderBorderRadius
border: $imageSliderBorder
height: $imageSliderWidth
width: $imageSliderHeight
cursor: $imageSliderCursor

View File

@ -45,7 +45,10 @@ Template[defaultTemplate].helpers({
return;
}
var timepointReviewers = Reviewers.findOne({timepointId: timepoint.timepointId});
var timepointReviewers = Reviewers.findOne({
timepointId: timepoint.timepointId
});
if (!timepointReviewers) {
return;
}
@ -59,7 +62,7 @@ function getReviewerTipText(reviewers) {
return;
}
var newReviewers = reviewers.filter(function ( reviewer ) {
var newReviewers = reviewers.filter(function(reviewer) {
return reviewer.userId !== Meteor.userId();
});
@ -76,6 +79,7 @@ function getReviewerTipText(reviewers) {
if (index > 0) {
tipText += ',';
}
tipText += reviewer.userName;
});
@ -96,4 +100,4 @@ WorklistTabs.find().observe({
});
}
}
});
});

View File

@ -0,0 +1,43 @@
<template name="radialProgressBar">
<div class="radialProgress">
<svg id="svg"
width="46"
height="46"
viewPort="0 0 50 50"
version="1.1"
xmlns="http://www.w3.org/2000/svg">
<!-- stroke-dasharray is 2 * PI * radius !-->
<circle r="13"
cx="23"
cy="23"
fill="transparent"
stroke-dasharray="81.64"
stroke-dashoffset="0">
</circle>
<circle id="bar"
r="13"
cx="23"
cy="23"
fill="transparent"
stroke-dasharray="81.64"
style="stroke-dashoffset: {{progressRadius}}px;">
</circle>
</svg>
<div class="progressText">
{{ #if isLocked }}
<svg>
<use xlink:href=/packages/lesiontracker/assets/icons.svg#icon-status-lock></use>
</svg>
{{ else }}
{{ #if progressComplete }}
<svg>
<use xlink:href=/packages/lesiontracker/assets/icons.svg#icon-status-complete></use>
</svg>
{{ else }}
{{progressText}}
{{ /if }}
{{ /if }}
</div>
</div>
</template>

View File

@ -0,0 +1,13 @@
Template.radialProgressBar.helpers({
progressComplete() {
const instance = Template.instance();
return instance.data.progressPercent === 100;
},
progressRadius() {
var radius = 13 * 2 * Math.PI;
const instance = Template.instance();
var percentLeft = (100 - instance.data.progressPercent) / 100;
return percentLeft * radius;
}
});

View File

@ -0,0 +1,41 @@
@import "{design}/app"
$circleSize = 46px
$progressTextColor = #6fbde2
.radialProgress
height: $circleSize
width: $circleSize
box-shadow: 0 0 1em black
border-radius: 100%
position: relative
#svg circle
stroke-dashoffset: 0
transition: stroke-dashoffset 1s linear
stroke: #203244
stroke-width: 3px
#svg #bar
stroke: $activeColor
-ms-transform: rotate(270deg); /* IE 9 */
-ms-transform-origin: center center; /* IE 9 */
-webkit-transform: rotate(270deg); /* Chrome, Safari, Opera */
-webkit-transform-origin: center center; /* Chrome, Safari, Opera */
transform: rotate(270deg);
transform-origin: center center;
.progressText
position: absolute;
width: $circleSize;
height: $circleSize;
line-height: $circleSize;
font-family: Roboto-Bold;
font-size: 17px;
text-align: center;
color: $progressTextColor;
top: 0;
svg
width: 17px
height: 17px

View File

@ -1,4 +1,3 @@
function parseUrl(url) {
var parser = document.createElement('a');
parser.href = url;
@ -11,14 +10,19 @@ Template.serverInformationModal.helpers({
var serviceInfo = Meteor.settings[defaultServiceType];
if (defaultServiceType === 'dicomWeb') {
var serverInformationDicom = [];
var endpoints = serviceInfo["endpoints"];
var endpoints = serviceInfo['endpoints'];
endpoints.forEach(function(endpoint) {
var parsedUrl = parseUrl(endpoint.qidoRoot);
serverInformationDicom.push({host: parsedUrl.hostname, port: parsedUrl.port, aeTitle: endpoint.name});
serverInformationDicom.push({
host: parsedUrl.hostname,
port: parsedUrl.port,
aeTitle: endpoint.name
});
});
return serverInformationDicom;
}
return serviceInfo;
}
});
});

View File

@ -0,0 +1,29 @@
<template name="studySeriesQuickSwitch">
<div class="quickSwitch {{side}}">
<div class="switchSection {{#if equals side "left"}}pull-right{{/if}} js-show-studies">
<div class="label">Study</div>
<div class="studySwitch">
</div>
</div>
<div class="switchSection {{#if equals side "left"}}pull-right{{/if}}">
<div class="label">Series</div>
<div class="seriesSwitch {{#if equals side "left"}}mirror-x{{/if}} js-show-series">
<svg viewBox="0 0 53 53">
<rect fill="#22364C" x="0" y="38" width="15" height="15" rx="3"></rect>
<rect fill="#22364C" x="19" y="38" width="15" height="15" rx="3"></rect>
<rect fill="#22364C" x="38" y="19" width="15" height="15" rx="3"></rect>
<rect fill="#22364C" x="19" y="19" width="15" height="15" rx="3"></rect>
<rect fill="#40D9B8" x="0" y="19" width="15" height="15" rx="3"></rect>
<rect fill="#22364C" x="38" y="0" width="15" height="15" rx="3"></rect>
<rect fill="#22364C" x="19" y="0" width="15" height="15" rx="3"></rect>
<rect fill="#22364C" x="0" y="0" width="15" height="15" rx="3"></rect>
</svg>
</div>
</div>
<div class="studyHover js-hide-studies {{ #unless studiesOpen}}hidden{{/unless}}">
</div>
<div class="seriesHover js-hide-series {{ #unless seriesOpen }}tiny{{/unless}}">
</div>
</div>
</template>

View File

@ -0,0 +1,34 @@
Template.studySeriesQuickSwitch.onCreated(function studySeriesQuickSwitchOnCreated() {
const instance = this;
instance.seriesOpen = new ReactiveVar(false);
instance.studiesOpen = new ReactiveVar(false);
});
Template.studySeriesQuickSwitch.events({
'mouseenter .js-show-series'(event, instance) {
instance.seriesOpen.set(true);
},
'mouseenter .js-show-studies'(event, instance) {
instance.studiesOpen.set(true);
},
'mouseleave .js-hide-series'(event, instance) {
instance.seriesOpen.set(false);
},
'mouseleave .js-hide-studies'(event, instance) {
instance.studiesOpen.set(false);
}
});
Template.studySeriesQuickSwitch.helpers({
seriesOpen() {
return Template.instance().seriesOpen.get();
},
studiesOpen() {
return Template.instance().studiesOpen.get();
}
});

View File

@ -0,0 +1,68 @@
@import "{design}/app"
$switchSize = 50px
.quickSwitch
position: absolute
width: 140px
height: 80px
top: 5px
div.switchSection
float: left
display: inline-block
margin: 0 5px
.label
display: block
text-transform: uppercase
text-align: center
font-size: 12px
font-weight: 100
color: $textSecondaryColor
.studySwitch
display: block
margin: 0 auto
width: $switchSize
height: $switchSize
border-radius: 11px
background-color: $darkUiColor
border: solid 1px $activeColor
.seriesSwitch
display: block
width: $switchSize
height: $switchSize
svg
width: $switchSize
height: $switchSize
.mirror-x
transform(scaleX(-1));
.studyHover
position: absolute
z-index: 10000
top: 70px
left: -350px
width: 400px
height: 500px
background: red
transition(all 0.3s ease)
.seriesHover
position: absolute
z-index: 10000
top: 70px
left: 50px
width: 700px
height: 900px
background: blue
transition(all 0.3s ease)
&.tiny
width: 0
height: 0
opacity: 0

View File

@ -0,0 +1,9 @@
<template name="studyTimepoint">
<div class="studyTimepointWrapper">
<div class="studyTimepoint">
{{#each study in (studies this.timepoint)}}
{{>studyTimepointStudy study=study}}
{{/each}}
</div>
</div>
</template>

View File

@ -0,0 +1,58 @@
// Initialize the timepoint wrapper max-height to enable CSS transition
Template.studyTimepoint.onRendered(() => {
const instance = Template.instance();
const $studies = instance.$('.studyTimepoint');
const $wrapper = $studies.closest('.studyTimepointWrapper');
const $timepoint = $wrapper.closest('.timepointEntry');
const studiesVisible = $studies.is(':visible');
if (!studiesVisible) {
$timepoint.addClass('active');
}
// Recalculates the timepoint height to make CSS transition smoother
$studies.trigger('displayStateChanged');
if (!studiesVisible) {
$timepoint.removeClass('active');
}
});
Template.studyTimepoint.events({
// Changes the selected study
'selectionChanged .studyTimepoint'(event, instance, selection) {
const $selection = $(selection);
const $thumbnails = $selection.find('.studyTimepointThumbnails');
const $timepoint = instance.$('.studyTimepoint');
// Set the max-height to inherit to be able to expand the wrapper on its full height
instance.$('.studyTimepointWrapper').css('max-height', 'inherit');
// Removes selected state from all studies but the triggered study
instance.$('.studyTimepointStudy').not(selection).removeClass('active');
// Toggle selected state for the triggered study
$selection.toggleClass('active');
// Recalculates the timepoint height to make CSS transition smoother
$thumbnails.one('transitionend', () => $timepoint.trigger('displayStateChanged'));
},
// It should be triggered when the timepoint height is changed
'displayStateChanged .studyTimepoint'(event, instance) {
const $timepoint = $(event.currentTarget);
const $wrapper = $timepoint.closest('.studyTimepointWrapper');
// Set the max-height for the wrapper to make CSS transition smoother
$wrapper.css('max-height', $timepoint.height());
}
});
Template.studyTimepoint.helpers({
studies: function(timepoint) {
const query = {
selected: true
};
return ViewerStudies.find(query);
}
});

View File

@ -0,0 +1,8 @@
@import "{design}/app"
.studyTimepointWrapper
overflow: hidden
transition($sidebarTransition)
.timepointEntry:not(.active) .studyTimepointWrapper
max-height: 0 !important

View File

@ -0,0 +1,29 @@
<template name="studyTimepointBrowser">
<div class="studyTimepointBrowser">
<div class="studyTimepointScrollArea">
<div class="p-x-1">
{{#each timepoint in timepoints}}
{{#if shouldShowTimepoint timepoint @index}}
<div class="timepointEntry p-y-2 active">
<div class="timepointHeader">
<div class="timepointDetails clearfix">
<div class="timepointFollowupTitle pull-left">
{{timepointTitle timepoint timepoints.count @index}}
</div>
<div class="expandIcon pull-right">
<i class="fa fa-chevron-down"></i>
</div>
<div class="timepointDate pull-right m-r-1">{{formatDA timepoint.earliestDate 'D-MMM-YYYY'}}</div>
</div>
<!-- TODO: [design] find out where to get this information -->
<div class="timepointModalities">2 MR, 2 CT</div>
</div>
{{>studyTimepoint timepoint=timepoint index=@index}}
</div>
<hr class="m-y-1">
{{/if}}
{{/each}}
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,46 @@
Template.studyTimepointBrowser.events({
'click .timepointHeader'(event, instance) {
const $timepoint = $(event.currentTarget).closest('.timepointEntry');
// Recalculates the timepoint height to make CSS transition smoother
$timepoint.find('.studyTimepoint').trigger('displayStateChanged');
// Toggle active class to group/ungroup timepoint studies
$timepoint.toggleClass('active');
}
});
Template.studyTimepointBrowser.helpers({
timepoints: function() {
const sort = {
sort: {
earliestDate: -1
}
};
return Timepoints.find({}, sort);
},
shouldShowTimepoint(timepoint, index) {
const instance = Template.instance();
if (instance.data.timepointViewType.get() === 'all') {
return true;
}
return index < 4 || timepoint.timepointType === 'baseline';
},
// Build the timepoint title based on its date
timepointTitle(timepoint, total, index) {
if (timepoint.timepointType === 'baseline') {
return 'Baseline';
}
const states = {
0: '(Current)',
1: '(Prior)',
2: '(Nadir)'
};
// TODO: [design] find out how to define the nadir timepoint
const followUp = total - index - 1;
const parenthesis = states[index] || '';
return `Follow-up ${followUp} ${parenthesis}`;
}
});

View File

@ -0,0 +1,68 @@
@import "{design}/app"
.studyTimepointBrowser
background-color: $primaryBackgroundColor
float: left
height: calc(100% - 55px - 50px - 30px)
overflow: hidden
width: 100%
.timepointEntry
&.active
.timepointModalities
height: 0
.timepointDate
display: none
.expandIcon i
transform: rotateX(180deg)
.timepointFollowupTitle
font-size: 14px
color: $textSecondaryColor
text-transform: uppercase
.timepointHeader
cursor: pointer
font-size: 13px
&
.expandIcon i
.timepointFollowupTitle
.timepointDate
.timepointModalities
color: $textSecondaryColor
transition($sidebarTransition)
&:hover
&
.timepointFollowupTitle
.expandIcon i
.timepointDate
.timepointModalities
color: $textPrimaryColor !important
.timepointDetails
.timepointModalities
line-height: 25px
.timepointModalities
height: 25px
overflow: hidden
.expandIcon i
color: $defaultColor
.timepointFollowupTitle
color: $textPrimaryColor
font-family: Roboto
padding-top: 2px
.studyTimepointScrollArea
height: 100%
overflow-x: hidden
overflow-y: auto
margin-right: -16px
padding-bottom: 20px
padding-right: 16px
&::-webkit-scrollbar
display: none

View File

@ -0,0 +1,18 @@
<template name="studyTimepointStudy">
<div class="studyTimepointStudy">
<div class="studyModality">
<div class="studyModalityBox">
{{this.study.modalities}}
</div>
<div class="studyModalityText">
<div class="studyModalityDate">{{formatDA this.study.studyDate 'D-MMM-YYYY'}}</div>
<div class="studyModalityDescription">{{this.study.studyDescription}}</div>
</div>
</div>
<div class="studyTimepointThumbnails">
{{#each thumbnail in (thumbnails this.study)}}
{{>thumbnailEntry thumbnail}}
{{/each}}
</div>
</div>
</template>

View File

@ -0,0 +1,40 @@
// Initialize the study wrapper max-height to enable CSS transition
Template.studyTimepointStudy.onRendered(() => {
const instance = Template.instance();
const $study = instance.$('.studyTimepointStudy');
const $thumbnails = instance.$('.studyTimepointThumbnails');
$study.addClass('active');
$thumbnails.css('max-height', $thumbnails.height());
$study.removeClass('active');
});
Template.studyTimepointStudy.events({
// Recalculates the timepoint height to make CSS transition smoother
'transitionend .studyTimepointThumbnails'(event, instance) {
if (event.target === event.currentTarget) {
$(event.currentTarget).closest('.studyTimepoint').trigger('displayStateChanged');
}
},
// Changes the current study selection for the clicked study
'click .studyModality'(event, instance) {
const $study = $(event.currentTarget).closest('.studyTimepointStudy');
const $timepoint = $study.closest('.studyTimepoint');
const study = $study[0];
$timepoint.trigger('selectionChanged', [study]);
}
});
Template.studyTimepointStudy.helpers({
thumbnails: function(study) {
var stacks = createStacks(study);
var array = [];
stacks.forEach(function(stack, index) {
array.push({
thumbnailIndex: index,
stack: stack
});
});
return array;
}
});

View File

@ -0,0 +1,99 @@
@import "{design}/app"
$boxBackgroundColor = #3E5975
$boxBorderColor = transparent
$boxHoverBackgroundColor = #14191E
$boxHoverBorderColor = #2d4660
$boxActiveBackgroundColor = $activeColor
$boxActiveTextColor = $primaryBackgroundColor
$boxActiveBorderColor = #131D29
$boxWidth = 50px
$borderThickness = 2px
$boxBorderRadius = 8px
$nestingMargin = 6px
$spacerX = 7px
$spacerY = 12px
.studyTimepointStudy
&.active
.studyModality
box-shadow: inset 0 0 0 3px $activeColor
.studyModalityBox
color: $boxActiveTextColor
&
&:before
&:after
background-color: $boxActiveBackgroundColor
border-color: $boxActiveBorderColor
&:not(.active) .studyTimepointThumbnails
max-height: 0 !important
.studyTimepointThumbnails
overflow: hidden
transition($sidebarTransition)
.studyModality
border: 1px solid $boxBorderColor
border-radius: 12px
cursor: pointer
font-family: Roboto
padding: $spacerY $spacerX ($spacerY - 1)
position: relative
transition($sidebarTransition)
z-index: 1
&:hover
background-color: $boxHoverBackgroundColor
border-color: $boxHoverBorderColor
.studyModalityText
font-size: 13px
left: ($spacerX * 3) + $boxWidth + ($nestingMargin * 3)
line-height: 14px
position: absolute
right: $spacerX
top: $spacerY
.studyModalityDate
margin-top: 8px
color: $textSecondaryColor
.studyModalityDescription
margin-top: 8px
color: $textPrimaryColor
.studyModalityBox
color: $textSecondaryColor
font-size: 22px
line-height: $boxWidth
margin-left: $nestingMargin * 2
margin-top: $nestingMargin * 2
position: relative
text-align: center
text-transform: uppercase
&
&:before
&:after
background-color: $boxBackgroundColor
border: $borderThickness solid $primaryBackgroundColor
border-radius: $boxBorderRadius
height: $boxWidth + ($borderThickness * 2)
transition($sidebarTransition)
width: $boxWidth + ($borderThickness * 2)
&:before
&:after
display: block
content: ''
position: absolute
&:before
top: -($borderThickness + $nestingMargin)
left: -($borderThickness + $nestingMargin)
z-index: -1
&:after
top: -($borderThickness + ($nestingMargin * 2))
left: -($borderThickness + ($nestingMargin * 2))
z-index: -2

View File

@ -1,10 +1,10 @@
<template name="timeoutCountdownDialog">
<div id="timeoutCountdownDialog">
<div class="dialogHeader">
<h5>Session is about to expire!</h5>
<h5>Your session is about to expire.</h5>
</div>
<div class="dialogContent">
<p>You will be log out in {{leftTime}} {{secondsText}}.</p>
<p>You will be logged out in {{timeLeft}}.</p>
</div>
</div>
</template>

View File

@ -1,9 +1,39 @@
Template.timeoutCountdownDialog.helpers({
leftTime: function(e) {
return Session.get("countdownDialogLeftTime");
},
secondsText: function() {
return Session.get("countdownDialogLeftTime") > 1? "seconds": "second";
timeLeft: function() {
var timeLeft = Session.get('countdownDialogLeftTime');
var suffix = timeLeft > 1 ? 'seconds': 'second';
return timeLeft + suffix;
}
});
});
Template.timeoutCountdownDialog.onRendered(function() {
// Show countdown dialog
var timeoutCountdownInterval;
$(document).on('TriggerOpenTimeoutCountdownDialog', function(e, leftTime) {
// Show modal dialog
timeoutCountdownInterval = setInterval(function() {
// Set countdownDialogLeftTime session
Session.set('countdownDialogLeftTime', --leftTime);
var dialog = $('#timeoutCountdownDialog');
// Show dialog
dialog.css('display', 'block');
}, 1000);
});
$(document).on('TriggerCloseTimeoutCountdownDialog', function() {
var dialog = $('#timeoutCountdownDialog');
// Close the dialog
dialog.css('display', 'none');
if (timeoutCountdownInterval) {
clearInterval(timeoutCountdownInterval);
// Remove reviewer info for the user
Meteor.call('removeUserFromReviewers', Meteor.userId());
}
});
});

View File

@ -0,0 +1,79 @@
<template name="toolbarSection">
<div class="toolbarSection">
<div id="studySidebarToggle" class="pull-left toolbarSectionButton">
<div class="capsule svgContainer">
<svg>
<use xlink:href="/packages/lesiontracker/assets/icons.svg#icon-studies"></use>
</svg>
</div>
<div class="buttonLabel">
Studies
</div>
</div>
<div class="pull-left">
{{ #each toolbarButtons }}
{{ >toolbarSectionButton }}
{{ /each }}
<div id="moreTools" class="toolbarSectionButton">
<div class="svgContainer">
<svg>
<use xlink:href="/packages/lesiontracker/assets/icons.svg#icon-tools-more"></use>
</svg>
</div>
<div class="buttonLabel">
More <i class="fa fa-caret-down" aria-hidden="true"></i>
</div>
</div>
</div>
{{ >studySeriesQuickSwitch side="left"}}
{{ >studySeriesQuickSwitch side="right"}}
<div class="pull-right toolbarSectionEntry">
{{ >caseProgress }}
</div>
<div class="capsule-group pull-right">
<div id="lesionSidebarToggle" class="capsule-entry">
<div class="svgContainer capsule">
<svg>
<use xlink:href="/packages/lesiontracker/assets/icons.svg#icon-measurements-lesions"></use>
</svg>
</div>
<div class="buttonLabel">
Lesions
</div>
</div>
<div id="additionalMeasurementsSidebarToggle" class="capsule-entry">
<div class="svgContainer capsule">
<svg>
<use xlink:href="/packages/lesiontracker/assets/icons.svg#icon-measurements-additional"></use>
</svg>
</div>
<div class="buttonLabel">
Additional
</div>
</div>
</div>
<div class="pull-right toolbarSectionButton">
<div class="svgContainer">
<svg>
<use xlink:href="/packages/lesiontracker/assets/icons.svg#icon-hud"></use>
</svg>
</div>
<div class="buttonLabel">
HUD
</div>
</div>
<div class="pull-right toolbarSectionButton">
<div class="svgContainer">
<svg>
<use xlink:href="/packages/lesiontracker/assets/icons.svg#icon-trial-info"></use>
</svg>
</div>
<div class="buttonLabel">
Trial
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,133 @@
Template.toolbarSection.onRendered(function() {
var instance = this;
instance.state = instance.data.state;
});
Template.toolbarSection.helpers({
toolbarButtons: function() {
var buttonData = [];
buttonData.push({
id: 'zoom',
title: 'Zoom',
classes: 'imageViewerTool',
svgLink: '/packages/lesiontracker/assets/icons.svg#icon-tools-zoom'
});
buttonData.push({
id: 'wwwc',
title: 'Levels',
classes: 'imageViewerTool',
svgLink: '/packages/lesiontracker/assets/icons.svg#icon-tools-levels'
});
buttonData.push({
id: 'pan',
title: 'Pan',
classes: 'imageViewerTool',
svgLink: '/packages/lesiontracker/assets/icons.svg#icon-tools-pan'
});
buttonData.push({
id: 'link',
title: 'Link',
classes: 'imageViewerCommand toolbarSectionButton',
svgLink: '/packages/lesiontracker/assets/icons.svg#icon-tools-link'
});
buttonData.push({
id: 'bidirectional',
title: 'Target',
classes: 'imageViewerTool',
svgLink: '/packages/lesiontracker/assets/icons.svg#icon-tools-measure-target'
});
buttonData.push({
id: 'nonTarget',
title: 'Non-Target',
classes: 'imageViewerTool toolbarSectionButton',
svgLink: '/packages/lesiontracker/assets/icons.svg#icon-tools-measure-non-target'
});
buttonData.push({
id: 'length',
title: 'Temp',
classes: 'imageViewerTool toolbarSectionButton',
svgLink: '/packages/lesiontracker/assets/icons.svg#icon-tools-measure-temp'
});
return buttonData;
}
});
Template.toolbarSection.events({
'click #studySidebarToggle': function(event, instance) {
var isOpen = instance.data.state.get('studySidebarOpen');
instance.data.state.set('studySidebarOpen', !isOpen);
},
'click #lesionSidebarToggle': function(event, instance) {
var isOpen = instance.data.state.get('lesionSidebarOpen');
instance.data.state.set('lesionSidebarOpen', !isOpen);
instance.data.state.set('additionalMeasurementsSidebarOpen', false);
},
'click #additionalMeasurementsSidebarToggle': function(event, instance) {
var isOpen = instance.data.state.get('additionalMeasurementsSidebarOpen');
instance.data.state.set('additionalMeasurementsSidebarOpen', !isOpen);
instance.data.state.set('lesionSidebarOpen', false);
},
// TODO: Inherit these from toolbar template somehow
'click .imageViewerTool': function(e) {
$(e.currentTarget).tooltip('hide');
var tool = e.currentTarget.id;
var elements = $('.imageViewerViewport');
var activeTool = toolManager.getActiveTool();
$('.toolbarSectionButton').removeClass('active');
if (tool === activeTool) {
var defaultTool = toolManager.getDefaultTool();
console.log('Setting active tool to: ' + defaultTool);
toolManager.setActiveTool(defaultTool, elements);
} else {
console.log('Setting active tool to: ' + tool);
toolManager.setActiveTool(tool, elements);
}
},
'click .imageViewerCommand': function(e) {
$(e.currentTarget).tooltip('hide');
var command = e.currentTarget.id;
if (!OHIF.viewer.functionList.hasOwnProperty(command)) {
return;
}
var activeViewport = Session.get('activeViewport');
var element = $('.imageViewerViewport').get(activeViewport);
OHIF.viewer.functionList[command](element);
}
});
Template.toolbarSection.onRendered(function() {
var tooltipButtons = $('[data-toggle="tooltip"]');
tooltipButtons.tooltip(OHIF.viewer.tooltipConfig);
// Enable tooltips for the layout button
var extraTooltipButtons = $('[rel="tooltip"]');
extraTooltipButtons.tooltip(OHIF.viewer.tooltipConfig);
// Set disabled/enabled tool buttons that are set in toolManager
var states = toolManager.getToolDefaultStates();
var disabledToolButtons = states.disabledToolButtons;
var allToolbarButtons = $('#toolbar').find('button');
if (disabledToolButtons && disabledToolButtons.length > 0) {
for (var i = 0; i < allToolbarButtons.length; i++) {
var toolbarButton = allToolbarButtons[i];
$(toolbarButton).prop('disabled', false);
var index = disabledToolButtons.indexOf($(toolbarButton).attr('id'));
if (index !== -1) {
$(toolbarButton).prop('disabled', true);
}
}
}
});

View File

@ -0,0 +1,151 @@
@import "{design}/app"
.toolbarSection
height: $toolbarHeight
width: 100%
border-bottom: $uiBorderColor $uiBorderThickness solid
.capsule-group
display: inline-block
div.capsule-entry
display: inline-block
color: $defaultColor
fill: $defaultColor
stroke: $defaultColor
height: $toolbarHeight
min-width: 30px
cursor: pointer
text-align: center
margin: 0
div.capsule
width: 66px
height: 26px
&:first-child
.capsule
border-radius: 57px
background-color: #16202b
border: solid 1px #3c5d80
border-right: none
border-top-right-radius: 0
border-bottom-right-radius: 0
&:last-child
.capsule
border-radius: 57px
background-color: #16202b
border: solid 1px #3c5d80
border-left: none
border-top-left-radius: 0
border-bottom-left-radius: 0
&.disabled
opacity: 0.5
cursor: not-allowed
.svgContainer
margin: 0 auto
text-align: center
.capsule
width: 62px
height: 26px
border-radius: 100px
background-color: #16202b
border: solid 1px #3c5d80
svg
background-color: transparent
margin: 5px
width: 17px
height: 17px
&:active, &.active
color: $activeColor
svg
fill: $activeColor
stroke: $activeColor
&:hover
color: $hoverColor
svg
fill: $hoverColor
stroke: $hoverColor
.toolbarSectionButton
display: inline-block
color: $defaultColor
fill: $defaultColor
stroke: $defaultColor
padding: 0 10px
height: $toolbarHeight
min-width: 30px
cursor: pointer
text-align: center
&.disabled
opacity: 0.5
cursor: not-allowed
.buttonLabel
font-size: 12px
.svgContainer
margin: 0 auto
text-align: center
.capsule
width: 62px
height: 26px
border-radius: 100px
background-color: #16202b
border: solid 1px #3c5d80
svg
background-color: transparent
margin: 2px
width: 21px
height: 21px
&:active, &.active
color: $activeColor
svg
fill: $activeColor
stroke: $activeColor
&:hover
color: $hoverColor
svg
fill: $hoverColor
stroke: $hoverColor
.toolbarSectionEntry
display: inline-block
color: $defaultColor
fill: $defaultColor
stroke: $defaultColor
padding: 0 10px
height: $toolbarHeight
min-width: 30px
cursor: pointer
text-align: center
.quickSwitch
position: absolute
&.middle
left: calc(50% - 60px)
&.left
left: calc(50% - 160px)
&.right
left: 50%
margin-left: 20px

View File

@ -0,0 +1,12 @@
<template name="toolbarSectionButton">
<div id="{{id}}" class="toolbarSectionButton {{classes}}" title="{{title}}">
<div class="svgContainer">
<svg>
<use xlink:href={{svgLink}}></use>
</svg>
</div>
<div class="buttonLabel">
{{title}}
</div>
</div>
</template>

View File

@ -1,38 +1,46 @@
<template name="userAccountMenu">
{{#if currentUser}}
<div id="userAccountMenu" class="row-fluid">
<div class="list-group panel">
<a href="#userMenu" class="list-group-item" id="userName" data-toggle="collapse" data-parent="#userAccountMenu">{{fullName}} {{currentPath}}<span class="caret"></span></a>
<div class="collapse pos-absolute" id="userMenu">
{{#if showWorklistMenu}}
<a href="worklist" id="worklist" data-toggle="collapse" data-target="#userMenu" class="list-group-item sub-item">
<div id="userAccountMenu" class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{name}} {{currentPath}} | <i class="fa fa-cog fa-lg"></i>
<span class="caret"></span>
</a>
<ul class="dropdown-menu dropdown-menu-right">
{{#if showWorklistMenu}}
<li>
<a href="worklist" id="worklist">
<i class="fa fa-list-ul"></i> Worklist
</a>
{{else}}
<!--TODO: Move this elsewhere in the Worklist? -->
<span data-toggle="collapse" data-target="#userMenu" class="list-group-item sub-item externalItem">{{>optionsButton }}</span>
<a href="audit" id="audit" data-toggle="collapse" data-target="#userMenu" class="list-group-item sub-item">
</li>
{{else}}
<!--TODO: Move this elsewhere in the Worklist? -->
<li>
{{>optionsButton }}
</li>
<li>
<a href="/audit" id="audit">
<i class="fa fa-list-ul"></i> View Audit Log
</a>
{{/if}}
<a href="#itemSettings" class="list-group-item sub-item" data-toggle="collapse" data-parent="#itemSettings"><i class="fa fa-cog fa-lg"></i> Settings <span class="caret"></span></a>
<div class="collapse list-group-submenu" id="itemSettings">
<a href="#" id="serverInformation" class="list-group-item sub-sub-item" data-parent="#itemSettings" data-toggle="collapse" data-target="#itemSettings,#userMenu"><i class="fa fa-server fa-lg"></i> Server Information</a>
</div>
<a href="#itemProfile" class="list-group-item sub-item" data-toggle="collapse" data-parent="#itemProfile" ><i class="fa fa-user fa-lg"></i> Profile <span class="caret"></span></a>
<div class="collapse list-group-submenu" id="itemProfile">
<a href="changePassword" id="changePassword" class="list-group-item sub-sub-item" data-parent="#itemProfile" data-toggle="collapse" data-target="#itemProfile,#userMenu"><i class="fa fa-lock"></i> Change Password</a>
</div>
<a href="#" id="logoutButton" data-toggle="collapse" data-target="#userMenu" class="list-group-item sub-item">
</li>
{{/if}}
<li role="separator" class="divider"></li>
<li>
<a id="serverInformation">
<i class="fa fa-server fa-lg"></i> Server Information
</a>
</li>
<li role="separator" class="divider"></li>
<li>
<a href="changePassword" id="changePassword">
<i class="fa fa-lock"></i> Change Password
</a>
</li>
<li>
<a id="logoutButton">
<i class="fa fa-power-off"></i> Logout
</a>
</div>
</div>
</li>
</ul>
</div>
{{/if}}
</template>

View File

@ -1,12 +1,15 @@
Template.userAccountMenu.helpers({
fullName: function() {
return Meteor.user().profile.fullName;
name: function() {
var nameSplit = Meteor.user().profile.fullName.split(' ');
var lastName = nameSplit[nameSplit.length - 1];
nameSplit[nameSplit.length - 1] = lastName.substr(0, 1) + '.';
return nameSplit.join(' ');
},
currentUser: function() {
var verifyEmail = Meteor.settings && Meteor.settings.public && Meteor.settings.public.verifyEmail || false;
if (!Meteor.user() || !Meteor.userId()) {
return false;
return;
}
if (!verifyEmail) {
@ -17,18 +20,13 @@ Template.userAccountMenu.helpers({
return true;
}
if (Meteor.user().emails[0].verified) {
return true;
}
return false;
return !!Meteor.user().emails[0].verified;
},
showWorklistMenu: function() {
var currentPath = Router.current().route.path(this);
if (currentPath === '/' || currentPath === '/worklist') {
return false;
if (currentPath !== '/' && currentPath !== '/worklist') {
return true;
}
return true;
}
});
@ -39,8 +37,62 @@ Template.userAccountMenu.events({
'click #logoutButton': function() {
// Remove reviewers info for the user
Meteor.call('removeUserFromReviewers', Meteor.userId());
Meteor.logout(function() {
Router.go('/entrySignIn');
});
}
});
Template.userAccountMenu.onRendered(function() {
var oldUserId;
var lastLoginModalInterval;
this.autorun(function() {
// Hook login/logout
var user = Meteor.user();
var newUserId = user._id;
if (oldUserId === null && newUserId) {
Session.set('showLastLoginModal', true);
// Log
HipaaLogger.logEvent({
eventType: 'init',
userId: user._id,
userName: user.fullName
});
} else if (newUserId === null && oldUserId) {
// Set showLastLoginModal as null
Session.set('showLastLoginModal', null);
// Destroy interval for last login modal
Meteor.clearInterval(lastLoginModalInterval);
console.log('The user logged out');
// Log
// TODO: eventType is not defined for logout in hipaa-audit-log
/*HipaaLogger.logEvent({
eventType: 'logout',
userId: oldUserId,
userName: userName
});*/
// Remove the user from Reviewers
Meteor.call('removeUserFromReviewers', oldUserId);
}
oldUserId = Meteor.userId();
// Trigger last login date popup
if (Session.get('showLastLoginModal')) {
Modal.show('lastLoginModal');
lastLoginModalInterval = Meteor.setInterval(function() {
Modal.hide('lastLoginModal');
Session.set('showLastLoginModal', null);
}, 3000);
}
});
});

View File

@ -1,34 +1,15 @@
@import "{design}/app"
#userAccountMenu
position: relative
float: right
padding: 5px
height: 100%
line-height: 40px
z-index: 999
.list-group
background-color: transparent
color: white
line-height: 35px
margin-left: 10px
.list-group-item
padding: 5px 10px
width: 200px
a#userName
padding: 0
background-color: transparent
color: white
border: solid 1px transparent
text-align: right
display: inline-block
width: auto
#userMenu
position: absolute
right: 0
.externalItem
a
color: #555
.dropdown-toggle
cursor: pointer
text-decoration: none
background-color: $primaryBackgroundColor
color: $textSecondaryColor
.list-group-submenu
.sub-sub-item
padding-left: 20px
padding-right: 20px
ul.dropdown-menu
li
cursor: pointer

View File

@ -5,12 +5,10 @@
{{>lesionLocationDialog}}
{{>nonTargetLesionDialog}}
{{>nonTargetResponseDialog}}
{{>conformanceCheckFeedback}}
{{>hidingPanel}}
<div id="viewportAndLesionTable">
{{>viewerMain }}
{{>lesionTable }}
</div>
<!-- >conformanceCheckFeedback -->
{{ >toolbarSection }}
{{ >flexboxLayout }}
</div>
{{else}}
{{>loadingText}}

View File

@ -1,4 +1,7 @@
Session.setDefault('activeViewport', false);
Session.setDefault('studySidebarOpen', false);
Session.setDefault('lesionSidebarOpen', false);
Session.setDefault('additionalMeasurementsSidebarOpen', false);
Template.viewer.onCreated(function() {
// Attach the Window resize listener
@ -6,10 +9,15 @@ Template.viewer.onCreated(function() {
ValidationErrors.remove({});
var self = this;
var instance = this;
instance.data.state = new ReactiveDict();
instance.data.state.set('studySidebarOpen', Session.get('studySidebarOpen'));
instance.data.state.set('lesionSidebarOpen', Session.get('lesionSidebarOpen'));
instance.data.state.set('additionalMeasurementsSidebarOpen', Session.get('additionalMeasurementsSidebarOpen'));
var contentId = this.data.contentId;
OHIF = OHIF || {
OHIF = OHIF || window.OHIF || {
viewer: {}
};
@ -100,7 +108,7 @@ Template.viewer.onCreated(function() {
Session.set('activeViewport', ViewerData[contentId].activeViewport || false);
// Set lesion tool buttons as disabled if pixel spacing is not available for active element
self.autorun(pixelSpacingAutorunCheck);
instance.autorun(pixelSpacingAutorunCheck);
// Update the ViewerStudies collection with the loaded studies
ViewerStudies.remove({});
@ -113,18 +121,17 @@ Template.viewer.onCreated(function() {
var patientId = this.data.studies[0].patientId;
Session.set('patientId', patientId);
self.autorun(function() {
instance.autorun(function() {
var dataContext = Template.currentData();
self.subscribe('singlePatientAssociatedStudies', dataContext.studies[0].patientId);
self.subscribe('singlePatientTimepoints', dataContext.studies[0].patientId);
self.subscribe('singlePatientMeasurements', dataContext.studies[0].patientId);
self.subscribe('singlePatientImageMeasurements', dataContext.studies[0].patientId);
instance.subscribe('singlePatientAssociatedStudies', dataContext.studies[0].patientId);
instance.subscribe('singlePatientTimepoints', dataContext.studies[0].patientId);
instance.subscribe('singlePatientMeasurements', dataContext.studies[0].patientId);
instance.subscribe('singlePatientImageMeasurements', dataContext.studies[0].patientId);
var subscriptionsReady = self.subscriptionsReady();
var subscriptionsReady = instance.subscriptionsReady();
log.info('autorun viewer.js. Ready: ' + subscriptionsReady);
if (subscriptionsReady) {
// Set buttons as enabled/disabled when Timepoints collection is ready
timepointAutoCheck(dataContext);
@ -241,7 +248,7 @@ Template.viewer.onCreated(function() {
// Set reviewer for this timepoint
if (data.timepoints[timepointId].studyInstanceUid) {
Meteor.call('setReviewer',data.timepoints[timepointId].studyInstanceUid);
Meteor.call('setReviewer', data.timepoints[timepointId].studyInstanceUid);
}
});

View File

@ -1,10 +1,12 @@
.viewerMain
width: 100%
height: 84%
@import "{design}/app"
#viewer
width: 100%
height: 100%
margin: 0
padding: 0
background-color: black
#layoutManagerTarget
width: 100%
height: calc(100% - 30px)
//font awesome icons
.fa-cr:before
@ -20,4 +22,14 @@
.fa-ex:before
content: 'EX'
font-weight: bold
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif
#btnCollapse
background-color: #333
border-color: black
&.active
border-color: #333
.btnCollapseIcon
color: white

View File

@ -1,6 +1,5 @@
<template name="viewerMain">
<div class="viewerMain">
{{ >toolbar toolbarOptions=toolbarOptions }}
<div id='layoutManagerTarget'>
</div>
</div>

View File

@ -0,0 +1,10 @@
Template.viewerMain.onRendered(function() {
var instance = this;
var studies = instance.data.studies;
var parentElement = instance.$("#layoutManagerTarget").get(0);
window.layoutManager = new LayoutManager(parentElement, studies);
ProtocolEngine = new HP.ProtocolEngine(layoutManager, studies);
HP.setEngine(ProtocolEngine);
});

View File

@ -0,0 +1,17 @@
@import "{design}/app"
.viewerMain
width: 100%
height: 100%
#layoutManagerTarget
width: 100%
height: 100%
transition(all 0.3s ease)
#imageViewerViewports
.viewportContainer:not(:last-child)
.removable
.imageViewerViewport
border-right: $uiBorderThickness solid #436270

View File

@ -18,7 +18,7 @@ toggleLesionTrackerTools = function() {
activate: [ 'deleteLesionKeyboardTool' ],
deactivate: [],
enable: [],
disable: [ 'bidirectional', 'nonTarget', 'scaleOverlayTool', 'length', 'crTool', 'unTool', 'exTool' ]
disable: [ 'bidirectional', 'nonTarget', 'length', 'crTool', 'unTool', 'exTool' ]
};
toolManager.setToolDefaultStates(toolDefaultStates);

View File

@ -6,12 +6,7 @@
* @param title The title to be used for the tab heading
*/
openNewTabWithTimepoint = function(timepointId, title) {
log.info('openNewTabWithTimepoint: ' + timepointId + ' ' + title);
// Generate a unique ID to represent this tab
// We can't just use the Mongo entry ID because
// then it will change after hot-reloading.
var contentid = uuid.new();
var contentid = 'viewerTab';
var timepoint = Timepoints.findOne({
timepointId: timepointId
@ -27,13 +22,7 @@ openNewTabWithTimepoint = function(timepointId, title) {
throw 'No studies found that are related to this timepoint';
}
// Create a new entry in the WorklistTabs Collection
WorklistTabs.insert({
title: title,
contentid: contentid,
active: false,
timepointId: timepointId
});
ViewerData = window.ViewerData || ViewerData;
// Update the ViewerData global object
ViewerData[contentid] = {

View File

@ -17,7 +17,7 @@ function dblClickOnStudy(data) {
// Check if the study has been associated, and if not, just open it on its own
if (!study) {
// Open a new tab with this study
openNewTab(data.studyInstanceUid, title);
open(data.studyInstanceUid, title);
return;
}
@ -29,7 +29,7 @@ function dblClickOnStudy(data) {
});
if (!timepoint) {
openNewTab(data.studyInstanceUid, title);
open(data.studyInstanceUid, title);
return;
}
@ -37,4 +37,36 @@ function dblClickOnStudy(data) {
title += ' ' + getTimepointName(timepoint);
openNewTabWithTimepoint(timepoint.timepointId, title);
}
}
/**
* Opens a study
*
* @param studyInstanceUid The UID of the Study to be opened
* @param title The title to be used for the tab heading
*/
function open(studyInstanceUid, title) {
var contentid = 'viewerTab';
WorklistTabs.remove({});
// Create a new entry in the WorklistTabs Collection
WorklistTabs.insert({
title: title,
contentid: contentid,
studyInstanceUid: studyInstanceUid,
active: false
});
ViewerData = window.ViewerData || ViewerData;
// Update the ViewerData global object
ViewerData[contentid] = {
title: title,
contentid: contentid,
studyInstanceUids: [studyInstanceUid]
};
// Switch to the new tab
switchToTab(contentid);
}

View File

@ -7,9 +7,11 @@ Package.describe({
Package.onUse(function(api) {
api.versionsFrom('1.2.0.2');
api.use('ecmascript');
api.use('standard-app-packages');
api.use('jquery');
api.use('stylus');
api.use('design');
api.use('validatejs');
@ -33,37 +35,68 @@ Package.onUse(function(api) {
api.addFiles('client/collections/LocationResponses.js', 'client');
api.addFiles('client/collections/subscriptions.js', 'client');
// Additional Custom Cornerstone Tools for Lesion Tracker
api.addFiles('client/compatibility/bidirectionalTool.js', 'client', {
bare: true
});
api.addFiles('client/compatibility/nonTargetTool.js', 'client', {
bare: true
});
api.addFiles('client/compatibility/scaleOverlayTool.js', 'client', {
bare: true
});
api.addFiles('client/compatibility/deleteLesionKeyboardTool.js', 'client', {
bare: true
});
api.addFiles('client/compatibility/crunexTool.js', 'client', {
bare: true
});
api.addFiles('client/compatibility/crTool.js', 'client', {
bare: true
});
api.addFiles('client/compatibility/unTool.js', 'client', {
bare: true
});
api.addFiles('client/compatibility/exTool.js', 'client', {
api.addFiles([
'client/compatibility/bidirectionalTool.js',
'client/compatibility/nonTargetTool.js',
'client/compatibility/scaleOverlayTool.js',
'client/compatibility/deleteLesionKeyboardTool.js',
'client/compatibility/crunexTool.js',
'client/compatibility/crTool.js',
'client/compatibility/unTool.js',
'client/compatibility/exTool.js'
], 'client', {
bare: true
});
api.addAssets('assets/icons.svg', 'client');
// UI Components
api.addFiles('client/components/viewer/viewer.html', 'client');
api.addFiles('client/components/viewer/viewer.styl', 'client');
api.addFiles('client/components/viewer/viewer.js', 'client');
api.addFiles('client/components/flexboxLayout/flexboxLayout.html', 'client');
api.addFiles('client/components/flexboxLayout/flexboxLayout.styl', 'client');
api.addFiles('client/components/flexboxLayout/flexboxLayout.js', 'client');
api.addFiles('client/components/toolbarSectionButton/toolbarSectionButton.html', 'client');
api.addFiles('client/components/toolbarSectionButton/toolbarSectionButton.styl', 'client');
api.addFiles('client/components/toolbarSectionButton/toolbarSectionButton.js', 'client');
api.addFiles('client/components/toolbarSection/toolbarSection.html', 'client');
api.addFiles('client/components/toolbarSection/toolbarSection.styl', 'client');
api.addFiles('client/components/toolbarSection/toolbarSection.js', 'client');
api.addFiles('client/components/radialProgressBar/radialProgressBar.html', 'client');
api.addFiles('client/components/radialProgressBar/radialProgressBar.styl', 'client');
api.addFiles('client/components/radialProgressBar/radialProgressBar.js', 'client');
api.addFiles('client/components/caseProgress/caseProgress.html', 'client');
api.addFiles('client/components/caseProgress/caseProgress.styl', 'client');
api.addFiles('client/components/caseProgress/caseProgress.js', 'client');
api.addFiles('client/components/viewerMain/viewerMain.html', 'client');
api.addFiles('client/components/viewerMain/viewerMain.styl', 'client');
api.addFiles('client/components/viewerMain/viewerMain.js', 'client');
api.addFiles('client/components/lesionTracker/lesionTracker.html', 'client');
api.addFiles('client/components/lesionTracker/lesionTracker.styl', 'client');
api.addFiles('client/components/lesionTracker/lesionTracker.js', 'client');
api.addFiles('client/components/lesionTrackerLayout/lesionTrackerLayout.html', 'client');
api.addFiles('client/components/lesionTrackerLayout/lesionTrackerLayout.styl', 'client');
api.addFiles('client/components/lesionTrackerLayout/lesionTrackerLayout.js', 'client');
api.addFiles('client/components/additionalMeasurements/additionalMeasurements.html', 'client');
api.addFiles('client/components/additionalMeasurements/additionalMeasurements.styl', 'client');
api.addFiles('client/components/additionalMeasurements/additionalMeasurements.js', 'client');
api.addFiles('client/components/additionalMeasurements/radioOptionGroup/radioOptionGroup.html', 'client');
api.addFiles('client/components/additionalMeasurements/radioOptionGroup/radioOptionGroup.styl', 'client');
api.addFiles('client/components/additionalMeasurements/radioOptionGroup/radioOptionGroup.js', 'client');
api.addFiles('client/components/studySeriesQuickSwitch/studySeriesQuickSwitch.html', 'client');
api.addFiles('client/components/studySeriesQuickSwitch/studySeriesQuickSwitch.styl', 'client');
api.addFiles('client/components/studySeriesQuickSwitch/studySeriesQuickSwitch.js', 'client');
api.addFiles('client/components/associationModal/associationModal.html', 'client');
api.addFiles('client/components/associationModal/associationModal.styl', 'client');
@ -91,14 +124,19 @@ Package.onUse(function(api) {
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.html', 'client');
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.js', 'client');
api.addFiles('client/components/lesionLocationDialog/lesionLocationDialog.styl', 'client');
api.addFiles('client/components/lesionTable/lesionTable.html', 'client');
api.addFiles('client/components/lesionTable/lesionTable.styl', 'client');
api.addFiles('client/components/lesionTable/lesionTable.js', 'client');
api.addFiles('client/components/lesionTableRow/lesionTableRow.html', 'client');
api.addFiles('client/components/lesionTableRow/lesionTableRow.styl', 'client');
api.addFiles('client/components/lesionTableRow/lesionTableRow.js', 'client');
api.addFiles('client/components/lesionTableHeaderRow/lesionTableHeaderRow.html', 'client');
api.addFiles('client/components/lesionTableHeaderRow/lesionTableHeaderRow.styl', 'client');
api.addFiles('client/components/lesionTableHeaderRow/lesionTableHeaderRow.js', 'client');
api.addFiles('client/components/lesionTableTimepointCell/lesionTableTimepointCell.html', 'client');
api.addFiles('client/components/lesionTableTimepointCell/lesionTableTimepointCell.styl', 'client');
api.addFiles('client/components/lesionTableTimepointCell/lesionTableTimepointCell.js', 'client');
@ -115,10 +153,20 @@ Package.onUse(function(api) {
api.addFiles('client/components/studyDateList/studyDateList.styl', 'client');
api.addFiles('client/components/studyDateList/studyDateList.js', 'client');
api.addFiles('client/components/studyTimepointBrowser/studyTimepoint.html', 'client');
api.addFiles('client/components/studyTimepointBrowser/studyTimepoint.styl', 'client');
api.addFiles('client/components/studyTimepointBrowser/studyTimepoint.js', 'client');
api.addFiles('client/components/studyTimepointBrowser/studyTimepointBrowser.html', 'client');
api.addFiles('client/components/studyTimepointBrowser/studyTimepointBrowser.styl', 'client');
api.addFiles('client/components/studyTimepointBrowser/studyTimepointBrowser.js', 'client');
api.addFiles('client/components/studyTimepointBrowser/studyTimepointStudy.html', 'client');
api.addFiles('client/components/studyTimepointBrowser/studyTimepointStudy.styl', 'client');
api.addFiles('client/components/studyTimepointBrowser/studyTimepointStudy.js', 'client');
api.addFiles('client/components/studyAssociationTable/studyAssociationTable.html', 'client');
api.addFiles('client/components/studyAssociationTable/studyAssociationTable.styl', 'client');
api.addFiles('client/components/studyAssociationTable/studyAssociationTable.js', 'client');
api.addFiles('client/components/conformanceCheckFeedback/conformanceCheckFeedback.html', 'client');
api.addFiles('client/components/conformanceCheckFeedback/conformanceCheckFeedback.styl', 'client');
api.addFiles('client/components/conformanceCheckFeedback/conformanceCheckFeedback.js', 'client');
@ -236,5 +284,4 @@ Package.onUse(function(api) {
api.export('Studies', [ 'client', 'server' ]);
api.export('Timepoints', [ 'client', 'server' ]);
api.export('Reviewers', [ 'client', 'server' ]);
});

View File

@ -1,5 +1,6 @@
<template name="imageThumbnail">
<div class="imageThumbnail">
<div class="imageThumbnailCanvas"></div>
<div class="imageThumbnailLoadingIndicator thumbnailLoadingIndicator">
<p>Loading {{percentComplete}}</p>
</div>
@ -7,4 +8,4 @@
<p>Error</p>
</div>
</div>
</template>
</template>

View File

@ -1,6 +1,6 @@
Template.imageThumbnail.onRendered(function() {
var instance = this.data.stack.instances[0];
var element = this.find('.imageThumbnail');
var element = this.find('.imageThumbnailCanvas');
cornerstone.disable(element);
$(element).find('canvas').remove();
@ -12,7 +12,7 @@ Template.imageThumbnail.onRendered(function() {
var elem = $(element);
elem.find('.imageThumbnailLoadingIndicator').css('display', 'block');
var thumbnailIndex = $('.imageThumbnail').index(element);
var thumbnailIndex = $('.imageThumbnailCanvas').index(element);
ThumbnailLoading[thumbnailIndex] = imageId;
this.data.thumbnailIndex = thumbnailIndex;

View File

@ -1,10 +1,30 @@
@import "{design}/app"
.thumbnailEntry.active .imageThumbnail
border-color: $activeColor
box-shadow: none
transition($sidebarTransition)
.imageThumbnail
width: 90px
height: 90px
background-color: black
background-color: $primaryBackgroundColor
border: 5px solid transparent
border-radius: 12px
box-shadow: inset 0 0 0 1px $uiBorderColorDark
height: 135px
margin: 0 auto
padding 1px 7px
position: relative
transition($sidebarTransition)
width: 217px
&:hover
border-color: $hoverColor
box-shadow: none
.imageThumbnailCanvas
height: 100%
overflow: hidden
.thumbnailLoadingIndicator
display: none
pointer-events: none
@ -17,7 +37,7 @@
bottom: 0
margin: auto
position: absolute
p
text-align: center
font-size: 10pt
font-size: 10pt

View File

@ -1,6 +1,22 @@
<template name="thumbnailEntry">
<div class="thumbnailEntry">
{{ >imageThumbnail }}
<div class='seriesDescription noselect'>{{seriesDescription}}</div>
<div class="thumbnailEntry m-t-1 m-b-2" data-uid="{{this.stack.seriesInstanceUid}}">
<div class="p-x-1">
{{>imageThumbnail}}
</div>
<div class="seriesDetails noselect m-a-1">
<div class="seriesDescription">
{{this.stack.seriesDescription}}
</div>
<div class="seriesInformation">
<div class="seriesInformationIcons">
<strong>S:</strong>
<div></div>
</div>
<div class="seriesInformationNumbers">
<div>{{this.stack.seriesNumber}}</div>
<div>{{this.stack.instances.length}}</div>
</div>
</div>
</div>
</div>
</template>
</template>

View File

@ -24,7 +24,7 @@ function thumbnailDragStartHandler(e, data) {
// Identify the current study and series index from the thumbnail's DOM position
var targetThumbnail = e.currentTarget;
var imageThumbnail = $(targetThumbnail);
// Store this data for use during drag and drop
OHIF.viewer.dragAndDropData = data;
@ -32,7 +32,7 @@ function thumbnailDragStartHandler(e, data) {
var targetId = targetThumbnail.id + 'DragClone';
var clone = cloneElement(targetThumbnail, targetId);
clone.classList.add('imageThumbnailClone');
// Set pointerEvents to pass through the clone DOM element
// This is necessary in order to identify what is below it
// when using document.elementFromPoint
@ -40,7 +40,7 @@ function thumbnailDragStartHandler(e, data) {
// Append the clone to the parent of the target
targetThumbnail.parentNode.appendChild(clone);
// Set the cursor x and y positions from the current touch/mouse coordinates
// Handle touchStart cases
if (e.type === 'touchstart') {
@ -61,7 +61,7 @@ function thumbnailDragStartHandler(e, data) {
// This block gets the current position of the touch/mouse
// relative to the thumbnail itself
//
//
// i.e. Where did the user grab it from?
var position = imageThumbnail.position();
var left = position.left;
@ -70,7 +70,7 @@ function thumbnailDragStartHandler(e, data) {
// This difference is saved for later so the element movement looks normal
diffX = cursorX - left;
diffY = cursorY - top;
// This sets the default style properties of the cloned element so it is
// ready to be dragged around the page
$(clone).css({
@ -103,7 +103,7 @@ function thumbnailDragHandler(e, target) {
'z-index': 100,
visibility: 'visible'
});
// Identify the element below the current cursor position
var elemBelow = document.elementFromPoint(cursorX + diffX, cursorY + diffY);
@ -156,7 +156,7 @@ function thumbnailDragEndHandler(e, target) {
// Remove any current faded effects on viewports
$('.imageViewerViewport canvas').removeClass('faded');
// If none exists, stop here
if (!elemBelow) {
return;
@ -186,8 +186,19 @@ function thumbnailDragEndHandler(e, target) {
var viewportIndex = $('.imageViewerViewport').index(element);
// Removes active class from previous selected series in viewport
const previousUid = layoutManager.viewportData[viewportIndex].seriesInstanceUid;
if (previousUid) {
$(`.thumbnailEntry[data-uid="${previousUid}"]`).removeClass('active');
}
// Rerender the viewport using the drag and drop data
layoutManager.rerenderViewportWithNewSeries(viewportIndex, OHIF.viewer.dragAndDropData);
// Set active state for dragged thumbnail entry
const newUid = OHIF.viewer.dragAndDropData.seriesInstanceUid
$(`.thumbnailEntry[data-uid="${newUid}"]`).addClass('active');
return false;
}
@ -213,9 +224,3 @@ Template.thumbnailEntry.events({
thumbnailDragEndHandler(e, e.currentTarget);
}
});
Template.thumbnailEntry.helpers({
seriesDescription: function() {
return this.stack.seriesDescription || '';
}
});

View File

@ -1,24 +1,66 @@
@import "{design}/app"
$seriesCountBackgroundColor = #678696
.thumbnailEntry
width: 105px
padding: 5px
background-color: black
border: 1px solid #424242
margin: 0 auto
cursor: copy
cursor: -webkit-grab
cursor: -moz-grab
.seriesDescription
text-align: center
color: #555
display: table
margin: 0 auto
font-family: Roboto
.seriesDetails
color: $textPrimaryColor
font-size: 14px
line-height: 1.3em
min-height: 36px
position: relative
word-wrap: break-word
&:hover
background-color: white
&.active
background-color: #424242
border-color: #4e4e4e
.seriesDescription
color: white
.seriesDescription
margin-right: 54px
.seriesInformation
position: absolute
right: 0
top: 0
.seriesInformationNumbers
.seriesInformationIcons
margin-right: 4px
.seriesInformationNumbers
color: $textSecondaryColor
float: left
font-size: 12px
.seriesInformationIcons
float: left
text-align: right
strong
color: $activeColor
font-size: 10px
// TODO: [design] try to find out what is the font-weight for Roboto-Black
font-weight: 500
div
background-color: $seriesCountBackgroundColor
margin-top: 6px
position: relative
&:after
background-color: $activeColor
box-shadow: 1px 1px rgba(0, 0, 0, .115)
left: -5px
position: absolute
top: -5px
&
&:after
border: 1px solid $primaryBackgroundColor
content: ''
display: block
height: 11px
width: 11px

Some files were not shown because too many files have changed in this diff Show More