AWV-3: Merging changes from upstream

This commit is contained in:
Bruno Alves de Faria 2016-09-03 14:47:39 -03:00
commit 176b503c5a
153 changed files with 2377 additions and 1125 deletions

View File

@ -13,3 +13,4 @@ notices-for-facebook-graph-api-2
1.3.0-split-minifiers-package
1.3.5-remove-old-dev-bundle-link
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package

View File

@ -4,18 +4,18 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
npm-bcrypt@0.8.7
npm-bcrypt@0.9.1
meteor-base@1.0.4 # Packages every Meteor app needs to have
mobile-experience@1.0.4 # Packages for a great mobile UX
mongo@1.1.10 # The database Meteor supports right now
mongo@1.1.12 # The database Meteor supports right now
blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views
session@1.1.6 # Client-side reactive dictionary for your app
jquery@1.11.9 # Helpful client-side library
jquery@1.11.9 # Helpful client-side library
tracker@1.1.0 # Meteor's client-side reactive programming library
es5-shim@4.6.13 # ECMAScript 5 compatibility for older browsers.
ecmascript@0.5.7 # Enable ECMAScript2015+ syntax in app code
es5-shim@4.6.14 # ECMAScript 5 compatibility for older browsers.
ecmascript@0.5.8 # Enable ECMAScript2015+ syntax in app code
clinical:router
clinical:extended-api
@ -26,6 +26,8 @@ clinical:fonts
clinical:hipaa-audit-log
clinical:hipaa-logger
aldeed:simple-schema # Third party package to deal with schemas
design
ohif:core
@ -34,15 +36,15 @@ cornerstone
worklist
viewerbase
dicomweb
accounts-base@1.2.9
accounts-password@1.2.12
accounts-base@1.2.11
accounts-password@1.3.0
ian:accounts-ui-bootstrap-3
twbs:bootstrap
http@1.2.8
http@1.2.9
arsnebula:reactive-promise
promise@0.8.3
stylus@2.513.4
promise@0.8.4
stylus@2.513.5
fortawesome:fontawesome
random@1.0.10
reactive-var@1.0.10
@ -56,11 +58,13 @@ check@1.2.3
aldeed:template-extension@4.0.0
zuuk:stale-session
gilbertwat:bootstrap3-daterangepicker
email@1.1.16
email@1.1.17
peppelg:bootstrap-3-modal
simple:reactive-method
hangingprotocols
fastclick@1.0.12
standard-minifier-css@1.1.8
standard-minifier-js@1.1.8
standard-minifier-css@1.2.0
standard-minifier-js@1.2.0
johdirr:meteor-git-rev
u2622:persistent-session
shell-server

View File

@ -1 +1 @@
METEOR@1.4.0.1
METEOR@1.4.1.1

View File

@ -1,21 +1,22 @@
accounts-base@1.2.9
accounts-password@1.2.12
accounts-base@1.2.11
accounts-password@1.3.0
aldeed:simple-schema@1.5.3
aldeed:template-extension@4.0.0
allow-deny@1.0.5
amplify@1.0.0
anti:gagarin@0.4.11
anti:i18n@0.4.3
arsnebula:reactive-promise@0.9.1
autoupdate@1.3.11
babel-compiler@6.9.0
babel-runtime@0.1.10
babel-compiler@6.9.1
babel-runtime@0.1.11
base64@1.0.9
binary-heap@1.0.9
blaze@2.1.8
blaze-html-templates@1.0.4
blaze-tools@1.0.9
boilerplate-generator@1.0.9
caching-compiler@1.1.6
caching-compiler@1.1.7
caching-html-compiler@1.0.6
callback-hook@1.0.9
check@1.2.3
@ -31,23 +32,23 @@ clinical:router-location@2.1.0
clinical:router-middleware-stack@2.1.2
clinical:router-url@2.1.0
clinical:theming@0.4.10
coffeescript@1.2.3
coffeescript@1.2.4_1
cornerstone@0.0.1
ddp@1.2.5
ddp-client@1.3.0
ddp-client@1.3.1
ddp-common@1.2.6
ddp-rate-limiter@1.0.5
ddp-server@1.3.9
ddp-server@1.3.10
deps@1.0.12
design@0.0.1
dicomweb@0.0.1
diff-sequence@1.0.6
dimseservice@0.0.1
ecmascript@0.5.7
ecmascript-runtime@0.3.12
ecmascript@0.5.8
ecmascript-runtime@0.3.14
ejson@1.0.12
email@1.1.16
es5-shim@4.6.13
email@1.1.17
es5-shim@4.6.14
fastclick@1.0.12
fortawesome:fontawesome@4.5.0
geojson-utils@1.0.9
@ -57,7 +58,7 @@ hangingprotocols@0.0.1
hot-code-push@1.0.4
html-tools@1.0.10
htmljs@1.0.10
http@1.2.8
http@1.2.9
ian:accounts-ui-bootstrap-3@1.2.89
id-map@1.0.8
insecure@1.0.7
@ -71,25 +72,25 @@ launch-screen@1.0.12
lesiontracker@0.0.1
livedata@1.0.18
localstorage@1.0.11
logging@1.1.14
logging@1.1.15
mdg:validation-error@0.5.1
meteor@1.2.16
meteor@1.2.17
meteor-base@1.0.4
meteor-platform@1.2.6
minifier-css@1.2.13
minifier-js@1.2.13
minifier-css@1.2.14
minifier-js@1.2.14
minimongo@1.0.17
mobile-experience@1.0.4
mobile-status-bar@1.0.12
modules@0.7.5
modules-runtime@0.7.5
modules@0.7.6
modules-runtime@0.7.6
momentjs:moment@2.14.4
mongo@1.1.10
mongo@1.1.12
mongo-id@1.0.5
mrt:moment@2.8.1
natestrauser:select2@4.0.2
npm-bcrypt@0.8.7
npm-mongo@1.5.45
natestrauser:select2@4.0.3
npm-bcrypt@0.9.1
npm-mongo@1.5.49
observe-sequence@1.0.12
ohif:core@0.0.1
ordered-dict@1.0.8
@ -97,7 +98,7 @@ orthanc-remote@0.0.1
peppelg:bootstrap-3-modal@1.0.4
practicalmeteor:chai@2.1.0_1
practicalmeteor:loglevel@1.2.0_2
promise@0.8.3
promise@0.8.4
random@1.0.10
rate-limit@1.0.5
reactive-dict@1.1.8
@ -109,27 +110,30 @@ rwatts:uuid@0.1.0
service-configuration@1.0.10
session@1.1.6
sha@1.0.8
shell-server@0.2.1
silentcicero:jszip@0.0.4
simple:reactive-method@1.0.2
spacebars@1.0.12
spacebars-compiler@1.0.12
srp@1.0.9
standard-app-packages@1.0.9
standard-minifier-css@1.1.8
standard-minifier-js@1.1.8
stylus@2.513.4
templating@1.2.13
standard-minifier-css@1.2.0
standard-minifier-js@1.2.0
stylus@2.513.5
templating@1.2.14
templating-tools@1.0.4
tracker@1.1.0
twbs:bootstrap@3.3.6
typ:accounts-ldap@1.0.1
typ:ldapjs@0.7.3
u2622:persistent-session@0.4.4
ui@1.0.11
underscore@1.0.9
url@1.0.10
validatejs@0.0.1
viewerbase@0.0.1
webapp@1.3.10
wadoproxy@0.0.1
webapp@1.3.11
webapp-hashing@1.0.9
worklist@0.0.1
zuuk:stale-session@1.0.8

View File

@ -16,7 +16,8 @@ var data = {
'optionsModal',
'serverInformationModal',
'confirmRemoveTimepointAssociation',
'lastLoginModal'
'lastLoginModal',
'viewSeriesDetailsModal'
]
};

View File

@ -13,3 +13,4 @@ notices-for-facebook-graph-api-2
1.3.0-split-minifiers-package
1.3.5-remove-old-dev-bundle-link
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package

View File

@ -5,20 +5,20 @@
# but you can also edit it by hand.
twbs:bootstrap
http@1.2.8
http@1.2.9
arsnebula:reactive-promise
promise@0.8.3
stylus@2.513.4
promise@0.8.4
stylus@2.513.5
fortawesome:fontawesome
mrt:moment
meteor-base@1.0.4
mobile-experience@1.0.4
mongo@1.1.10
mongo@1.1.12
blaze-html-templates@1.0.4
session@1.1.6
jquery@1.11.9
tracker@1.1.0
logging@1.1.14
logging@1.1.15
reload@1.1.10
random@1.0.10
ejson@1.0.12
@ -26,9 +26,10 @@ spacebars@1.0.12
check@1.2.3
cornerstone
dicomweb
aldeed:simple-schema # Third party package to deal with schemas
design
ohif:core
ecmascript@0.5.7
ecmascript@0.5.8
viewerbase
worklist
reactive-var@1.0.10
@ -37,7 +38,10 @@ hangingprotocols
reactive-dict@1.1.8
clinical:router
fastclick@1.0.12
standard-minifier-css@1.1.8
standard-minifier-js@1.1.8
aldeed:simple-schema
standard-minifier-css@1.2.0
standard-minifier-js@1.2.0
johdirr:meteor-git-rev
wadoproxy
aldeed:template-extension
peppelg:bootstrap-3-modal
shell-server

View File

@ -1 +1 @@
METEOR@1.4.0.1
METEOR@1.4.1.1

View File

@ -1,16 +1,17 @@
aldeed:simple-schema@1.5.3
aldeed:template-extension@4.0.0
allow-deny@1.0.5
arsnebula:reactive-promise@0.9.1
autoupdate@1.3.11
babel-compiler@6.9.0
babel-runtime@0.1.10
babel-compiler@6.9.1
babel-runtime@0.1.11
base64@1.0.9
binary-heap@1.0.9
blaze@2.1.8
blaze-html-templates@1.0.4
blaze-tools@1.0.9
boilerplate-generator@1.0.9
caching-compiler@1.1.6
caching-compiler@1.1.7
caching-html-compiler@1.0.6
callback-hook@1.0.9
check@1.2.3
@ -18,19 +19,19 @@ clinical:router@2.0.19
clinical:router-location@2.1.0
clinical:router-middleware-stack@2.1.2
clinical:router-url@2.1.0
coffeescript@1.2.3
coffeescript@1.2.4_1
cornerstone@0.0.1
ddp@1.2.5
ddp-client@1.3.0
ddp-client@1.3.1
ddp-common@1.2.6
ddp-server@1.3.9
ddp-server@1.3.10
deps@1.0.12
design@0.0.1
dicomweb@0.0.1
diff-sequence@1.0.6
dimseservice@0.0.1
ecmascript@0.5.7
ecmascript-runtime@0.3.12
ecmascript@0.5.8
ecmascript-runtime@0.3.14
ejson@1.0.12
fastclick@1.0.12
fortawesome:fontawesome@4.5.0
@ -40,7 +41,7 @@ hangingprotocols@0.0.1
hot-code-push@1.0.4
html-tools@1.0.10
htmljs@1.0.10
http@1.2.8
http@1.2.9
id-map@1.0.8
iron:controller@1.0.12
iron:core@1.0.11
@ -50,31 +51,32 @@ johdirr:meteor-git-rev@0.0.4
jquery@1.11.9
launch-screen@1.0.12
livedata@1.0.18
logging@1.1.14
mdg:validation-error@0.2.0
meteor@1.2.16
logging@1.1.15
mdg:validation-error@0.5.1
meteor@1.2.17
meteor-base@1.0.4
meteor-platform@1.2.6
minifier-css@1.2.13
minifier-js@1.2.13
minifier-css@1.2.14
minifier-js@1.2.14
minimongo@1.0.17
mobile-experience@1.0.4
mobile-status-bar@1.0.12
modules@0.7.5
modules-runtime@0.7.5
modules@0.7.6
modules-runtime@0.7.6
momentjs:moment@2.14.4
mongo@1.1.10
mongo@1.1.12
mongo-id@1.0.5
mrt:moment@2.8.1
natestrauser:select2@4.0.2
npm-mongo@1.5.45
natestrauser:select2@4.0.3
npm-mongo@1.5.49
observe-sequence@1.0.12
ohif:core@0.0.1
ordered-dict@1.0.8
orthanc-remote@0.0.1
peppelg:bootstrap-3-modal@1.0.4
practicalmeteor:chai@2.1.0_1
practicalmeteor:loglevel@1.2.0_2
promise@0.8.3
promise@0.8.4
random@1.0.10
reactive-dict@1.1.8
reactive-var@1.0.10
@ -83,14 +85,15 @@ retry@1.0.8
routepolicy@1.0.11
rwatts:uuid@0.1.0
session@1.1.6
shell-server@0.2.1
silentcicero:jszip@0.0.4
spacebars@1.0.12
spacebars-compiler@1.0.12
standard-app-packages@1.0.9
standard-minifier-css@1.1.8
standard-minifier-js@1.1.8
stylus@2.513.4
templating@1.2.13
standard-minifier-css@1.2.0
standard-minifier-js@1.2.0
stylus@2.513.5
templating@1.2.14
templating-tools@1.0.4
tracker@1.1.0
twbs:bootstrap@3.3.6
@ -99,6 +102,7 @@ underscore@1.0.9
url@1.0.10
validatejs@0.0.1
viewerbase@0.0.1
webapp@1.3.10
wadoproxy@0.0.1
webapp@1.3.11
webapp-hashing@1.0.9
worklist@0.0.1

View File

@ -27,11 +27,11 @@ $lesionsSidebarMenuWidth = 450px
transform(translateX(0%))
.sidebar-left
max-width: $studiesSidebarMenuWidth
order: 1
theme('border-right', '%s solid $uiBorderColor' % $uiBorderThickness)
flex: 1
margin-left: - $studiesSidebarMenuWidth
border-right: $uiBorderThickness $uiBorderColor solid
max-width: $studiesSidebarMenuWidth
order: 1
&.sidebar-open
margin-left: 0
@ -62,7 +62,7 @@ $lesionsSidebarMenuWidth = 450px
max-width: $lesionsSidebarMenuWidth + 270px
&:before
background-color: $uiBorderColor
theme('background-color', '$uiBorderColor')
content: ''
display: block
height: 100%
@ -75,6 +75,6 @@ $lesionsSidebarMenuWidth = 450px
margin-right: 0
.studiesListedChanger
theme('border-bottom', '%s solid $uiBorderColor' % $uiBorderThickness)
padding: 20px 10px
text-align: center
border-bottom: $uiBorderThickness $uiBorderColor solid

View File

@ -25,4 +25,5 @@
</div>
{{> worklist }}
{{> aboutModal }}
{{> viewSeriesDetailsModal }}
</template>

View File

@ -9,8 +9,8 @@ $expandedHeight = 160px
.topBar
height: $topBarHeight
color: $textPrimaryColor
background-color: $primaryBackgroundColor
theme('color', '$textPrimaryColor')
theme('background-color', '$primaryBackgroundColor')
transition(all 0.5s ease)
.brandSection
@ -26,14 +26,14 @@ $expandedHeight = 160px
display: inline-block
font-family: $logoFontFamily
font-weight: $logoFontWeight
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
line-height: 30px
.studyListLinkSection
border-left: $uiBorderThickness solid $uiBorderColor;
theme('border-left', '%s solid $uiBorderColor' % $uiBorderThickness)
.btn
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
cursor: pointer
font-size: 13px
font-weight: 500
@ -41,10 +41,10 @@ $expandedHeight = 160px
margin-top: 3px
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')
&.studyList
background-color: rgba(21, 25, 30, 0.7)
@ -75,5 +75,5 @@ $expandedHeight = 160px
.tab-pane
.viewerContainer
.loadingTextDiv
color: $textSecondaryColor
font-size: 30px
theme('color', '$textSecondaryColor')
font-size: 30px

View File

@ -1,7 +1,7 @@
@import "{design}/app"
.toolbarSection
border-bottom: $uiBorderColor $uiBorderThickness solid
theme('border-bottom', '%s solid $uiBorderColor' % $uiBorderThickness)
flex: 0 0 auto
height: $toolbarHeight
overflow: hidden
@ -11,4 +11,4 @@
width: 100%
&.expanded
height: $toolbarHeight + $toolbarDrawerHeight
height: $toolbarHeight + $toolbarDrawerHeight

View File

@ -6,5 +6,5 @@
width: 100%
.loadingTextDiv
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
font-size: 30px

View File

@ -26,7 +26,8 @@ Package.onUse(function (api) {
api.use([
'zuuk:stale-session@1.0.8',
'typ:accounts-ldap'
'typ:accounts-ldap',
'random'
], ['client', 'server']);
api.addFiles([

View File

@ -1,7 +1,7 @@
@import "{design}/app"
$height = 25px
$textColorActive = #2D2D2D
$textColorButtonActive = #2D2D2D
.roundedButtonGroup
@ -15,9 +15,9 @@ $textColorActive = #2D2D2D
.roundedButton
align-items: center
background-color: $uiGrayDark
border: 2px $uiBorderColorDark solid
color: $textSecondaryColor
theme('background-color', '$uiGrayDark')
theme('border', '2px solid $uiBorderColorDark')
theme('color', '$textSecondaryColor')
display: flex
font-size: 11px
font-weight: 500
@ -29,19 +29,19 @@ $textColorActive = #2D2D2D
transition: transition($sidebarTransition)
svg
fill: $defaultColor
stroke: $defaultColor
theme('fill', '$defaultColor')
theme('stroke', '$defaultColor')
svg
span
margin: 0 2px
i
line-height: 15px
font-size: 15px
.bottomLabel
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
font-size: 12px
font-weight: 500
line-height: 12px
@ -58,16 +58,16 @@ $textColorActive = #2D2D2D
border-top-right-radius: $height
&:hover .roundedButton
background-color: $boxBackgroundColor
color: $uiGrayDark
theme('color', '$uiGrayDark')
theme('background-color', '$boxBackgroundColor')
&.active .roundedButton
background-color: $activeColor
border-color: $uiBorderColorActive
color: $textColorActive
theme('background-color', '$activeColor')
theme('border-color', '$uiBorderColorActive')
color: $textColorButtonActive
&:hover .roundedButton
&.active .roundedButton
svg
fill: $uiGrayDark
stroke: $uiGrayDark
theme('fill', '$uiGrayDark')
theme('stroke', '$uiGrayDark')

View File

@ -18,7 +18,9 @@ Package.onUse(function(api) {
'styles/imports/mixins.styl',
'styles/imports/spacings.styl',
'styles/imports/variables.styl',
'styles/imports/theming.styl',
'styles/imports/theme-tide.styl',
'styles/imports/theme-tigerlilly.styl'
], 'client', {
isImport: true
});
@ -35,7 +37,8 @@ Package.onUse(function(api) {
api.addFiles([
'styles/components/radio.styl',
'styles/components/select2.styl',
'styles/components/selectTree.styl'
'styles/components/selectTree.styl',
'styles/components/states.styl'
], 'client');
// Rounded Button Group

View File

@ -2,11 +2,26 @@
html body
font-family: 'Roboto', 'OpenSans', 'HelveticaNeue-Light', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, 'Lucida Grande', sans-serif
// Try to fix LT-273, need a monitor to test on though.
//text-rendering: optimizeLegibility
html hr
border-top: 1px solid $uiBorderColor
theme('border-top', '1px solid $uiBorderColor')
label.form-group
width: 100%
&>span.select2
display: block
width: 100% !important
span.select2-selection
min-height: 34px
span.select2-selection__rendered
height: 34px
line-height: 34px
span.select2-selection__arrow
height: 34px
.center-table
display: table
@ -32,9 +47,9 @@ html hr
.modal-dialog
.modal-content
background: $primaryBackgroundColor
border-color: $uiBorderColor
color: $textSecondaryColor
theme('background', '$primaryBackgroundColor')
theme('border-color', '$uiBorderColor')
theme('color', '$textSecondaryColor')
.modal-header, .modal-footer
border-color: $uiBorderColor
theme('border-color', '$uiBorderColor')

View File

@ -3,12 +3,12 @@
.group-radio
label
cursor: pointer
input
height: 0
width: 0
visibility: hidden
span
padding-left: 23px
position: relative
@ -23,14 +23,14 @@
top: 50%
transform(translateY(-50%))
width: 16px
input:focus + span:before
// TODO: [design] define a outline for the design
theme('box-shadow', '0 0 2px 2px $textSecondaryColor')
outline: none
box-shadow: 0 0 2px 2px $textSecondaryColor
input:checked + span:after
background: $uiBorderColorDark
theme('background', '$uiBorderColorDark')
border-radius: 5px
content: ''
height: 10px

View File

@ -22,8 +22,8 @@ span.select2.select2-container
padding-left: 11px
span.select2-selection__arrow
theme('border-left', '1px solid $primaryBackgroundColor')
background-color: $gray1
border-left: 1px solid $primaryBackgroundColor
border-top-right-radius: $borderRadius
border-bottom-right-radius: $borderRadius
right: 0
@ -42,8 +42,8 @@ span.select2.select2-container
placeholder-color($gray4)
li.select2-selection__choice
theme('border-color', '$uiGray')
background-color: #FFF
border-color: $uiGray
border-radius: $borderRadius
color: $gray4
font-size: 12px

View File

@ -76,6 +76,7 @@ $gray6 = #303030
display: block
margin: 0
padding: 5px
position: relative
text-align: center
span
@ -85,6 +86,14 @@ $gray6 = #303030
input
border-color: $gray5
font-weight: normal
padding-left: 34px
& ~ i
color: $gray1
font-size: 18px
left: 16px
position: absolute
top: 37px
.tree-breadcrumb
line-height: 32px
@ -146,7 +155,6 @@ $gray6 = #303030
&.active
box-shadow: 0 0 0 200px white, inset 0 0 0 200px white
position: fixed
transition(box-shadow 0.3s ease)
z-index: 0
@ -172,7 +180,7 @@ $gray6 = #303030
border-radius: 5px
color: white
opacity: 0.9
padding: 5px 12px 12px
padding: 0 15px 12px
position: absolute
top: 50%
transform(translateY(-50%))
@ -180,13 +188,21 @@ $gray6 = #303030
h5.title
color: $gray5
font-size: 12px
font-weight: normal
line-height: 20px
margin: 0
margin: 12px 0 16px
label
cursor: pointer
display: block
font-weight: 300
line-height: 26px
margin: 0
padding-left: 3px
transition(padding 0.3s ease)
&:hover
padding-left: 0
.select-tree-root
@ -218,7 +234,7 @@ $gray6 = #303030
.content
margin-left: 0
&.interacted
&.interacted, &.selected
.select-tree-common
z-index: 1

View File

@ -0,0 +1,29 @@
@import "{design}/app"
.state-error
&+.tooltip
.tooltip-inner
color: white
theme('background-color', '$uiStateErrorBorder')
&.top .tooltip-arrow
theme('border-top-color', '$uiStateErrorBorder')
&.right .tooltip-arrow
theme('border-right-color', '$uiStateErrorBorder')
&.bottom .tooltip-arrow
theme('border-bottom-color', '$uiStateErrorBorder')
&.left .tooltip-arrow
theme('border-left-color', '$uiStateErrorBorder')
&:not(.component-group)
&.form-control, .form-control
theme('background-color', '$uiStateError')
theme('border-color', '$uiStateErrorBorder')
theme('color', '$uiStateErrorText')
.select2-selection
theme('background-color', '$uiStateError')
theme('border-color', '$uiStateErrorBorder')
theme('color', '$uiStateErrorText')

View File

@ -1,54 +1,34 @@
// Common pallete
$uiYellow = #E29E4A
$uiSkyBlue = #6FBDE2
$themes['tide'] = {
// Common pallete
$uiYellow: #E29E4A
$uiSkyBlue: #6FBDE2
$uiLightGray = #516873
$uiGray = #263340
$uiGrayDark = #16202B
$uiGrayDarker = #151A1F
$uiGrayDarkest = #14191E
// State pallete
$uiStateError: #FFCCCC
$uiStateErrorBorder: #993333
$uiStateErrorText: #661111
// Interface UI Colors
$defaultColor = #9cd0fe
$hoverColor = #ffffff
$activeColor = #00a4d9
$uiBorderColor = #436270
$uiBorderColorDark = #3C5D80
$uiBorderColorActive = #00a4d9
$primaryBackgroundColor = #000000
$boxBackgroundColor = #344a61
$boxBackgroundColorDark = #22374D
$uiLightGray: #516873
$uiGray: #263340
$uiGrayDark: #16202B
$uiGrayDarker: #151A1F
$uiGrayDarkest: #14191E
// Text Colors
$textColorActive = black //#89bae5
$textPrimaryColor = #ffffff
$textSecondaryColor = #6a8fb1
// Interface UI Colors
$defaultColor: #9cd0fe
$hoverColor: #ffffff
$activeColor: #00a4d9
$uiBorderColor: #436270
$uiBorderColorDark: #3C5D80
$uiBorderColorActive: #00a4d9
$primaryBackgroundColor: #000000
$boxBackgroundColor: #344a61
// Other Colors
$menuBackgroundColor = #F2F2F2
$imageSliderColor = #163239
// Text Colors
$textColorActive: black //#89bae5
$textPrimaryColor: #ffffff
$textSecondaryColor: #6a8fb1
// Viewport Settings
$viewportBorderThickness = 1px
$viewportBorderColor = $uiBorderColorDark
$viewportBorderColorHover = $uiBorderColor
$viewportBorderColorActive = $uiBorderColorActive
// Sizes
$topBarHeight = 40px
$toolbarHeight = 78px
$toolbarDrawerHeight = 62px
// Thicknesses
$uiBorderThickness = 1px
// Fonts
$logoFontFamily = "Sanchez"
$logoFontWeight = 300
// Transitions
$transitionDuration = 0.3s
$transitionEffect = ease
$sidebarTransition = all $transitionDuration $transitionEffect
$studiesSidebarMenuWidth = 307px
$lesionsSidebarMenuWidth = 323px
// Other Colors
$imageSliderColor: #163239
}

View File

@ -1,54 +1,34 @@
// Common pallete
$uiYellow = #E29E4A
$uiSkyBlue = #6FBDE2
$themes['tigerlilly'] = {
// Common pallete
$uiYellow: #E29E4A
$uiSkyBlue: #6FBDE2
$uiLightGray = #516873
$uiGray = #263340
$uiGrayDark = #16202B
$uiGrayDarker = #151A1F
$uiGrayDarkest = #14191E
// State pallete
$uiStateError: #FFCCCC
$uiStateErrorBorder: #CC6666
$uiStateErrorText: #661111
// Interface UI Colors
$defaultColor = #98ceff
$hoverColor = #ffffff
$activeColor = #ff8a3d
$uiBorderColor = #744b71
$uiBorderColorDark = #744b71
$uiBorderColorActive = #ff8a3d
$primaryBackgroundColor = #000000
$boxBackgroundColor = #5b3a59
$boxBackgroundColorDark = #4b2f40
$uiLightGray: #516873
$uiGray: #263340
$uiGrayDark: #16202B
$uiGrayDarker: #151A1F
$uiGrayDarkest: #14191E
// Text Colors
$textColorActive = #ff8a3d
$textPrimaryColor = #ffffff
$textSecondaryColor = #6a8fb1
// Interface UI Colors
$defaultColor: #98ceff
$hoverColor: #ffffff
$activeColor: #ff8a3d
$uiBorderColor: #744b71
$uiBorderColorDark: #744b71
$uiBorderColorActive: #ff8a3d
$primaryBackgroundColor: #000000
$boxBackgroundColor: #5b3a59
// Other Colors
$menuBackgroundColor = #F2F2F2
$imageSliderColor = #4b2c3c //#163239
// Text Colors
$textColorActive: black
$textPrimaryColor: #ffffff
$textSecondaryColor: #6a8fb1
// Viewport Settings
$viewportBorderThickness = 1px
$viewportBorderColor = $uiBorderColorDark
$viewportBorderColorHover = $uiBorderColor
$viewportBorderColorActive = $uiBorderColorActive
// Sizes
$topBarHeight = 40px
$toolbarHeight = 78px
$toolbarDrawerHeight = 62px
// Thicknesses
$uiBorderThickness = 1px
// Fonts
$logoFontFamily = "Sanchez"
$logoFontWeight = 300
// Transitions
$transitionDuration = 0.3s
$transitionEffect = ease
$sidebarTransition = all $transitionDuration $transitionEffect
$studiesSidebarMenuWidth = 307px
$lesionsSidebarMenuWidth = 323px
// Other Colors
$imageSliderColor: #4b2c3c //#163239
}

View File

@ -0,0 +1,32 @@
$themes = {}
$defaultTheme = 'tide'
@import "./theme-tide.styl"
@import "./theme-tigerlilly.styl"
parseSpaceVars($theme, $value)
$valueSplit = split(' ', $value)
$list = ''
pop($list)
for $property in $valueSplit
if ($theme[$property])
push($list, $theme[$property])
else
push($list, $property)
join(' ', $list)
parseCommaVars($theme, $value)
$valueSplit = split(',', $value)
$list = ''
pop($list)
for $sentence in $valueSplit
push($list, parseSpaceVars($theme, $sentence))
unquote(join(',', $list))
theme($property, $value)
/ {selector()}
{$property}: parseCommaVars($themes[$defaultTheme], $value)
for $themeName, $theme in $themes
for $selectorPiece in split(',', selector())
/ body.theme-{$themeName} {$selectorPiece}
{$property}: parseCommaVars($theme, $value)

View File

@ -1,2 +1,22 @@
//@import "./theme-tigerlilly.styl"
@import "./theme-tide.styl"
@import "./theming.styl"
// Sizes
$topBarHeight = 40px
$toolbarHeight = 78px
$toolbarDrawerHeight = 62px
$studiesSidebarMenuWidth = 307px
$lesionsSidebarMenuWidth = 323px
// Fonts
$logoFontFamily = "Sanchez"
$logoFontWeight = 300
// Transitions
$transitionDuration = 0.3s
$transitionEffect = ease
$sidebarTransition = all $transitionDuration $transitionEffect
// Thicknesses
$viewportBorderThickness = 1px
$uiBorderThickness = 1px

View File

@ -3,12 +3,12 @@
$height = 20px
#protocolEditor
theme('background', '$primaryBackgroundColor')
height: 100%
width: 450px
padding: 10px 0
position: absolute
top: 0
background: $primaryBackgroundColor
.navigationButtons
ul
@ -35,27 +35,27 @@ $height = 20px
cursor: pointer
text-decoration: none
outline: none
background-color: $uiGrayDark
border: 2px $uiBorderColorDark solid
color: $textSecondaryColor
theme('background-color', '$uiGrayDark')
theme('border', '2px solid $uiBorderColorDark')
theme('color', '$textSecondaryColor')
transition: transition($sidebarTransition)
border-radius: $height
&.active a
background-color: $activeColor
border-color: $uiBorderColorActive
color: $textColorActive
theme('background-color', '$activeColor')
theme('border-color', '$uiBorderColorActive')
theme('color', '$textColorActive')
transition: transition($sidebarTransition)
p
h2
h3
h4
color: $defaultColor
theme('color', '$defaultColor')
label
ul
color: $defaultColor
theme('color', '$defaultColor')
font-weight: 400
label
@ -68,7 +68,7 @@ $height = 20px
width: 40%
border: none
background: #212121
color: $defaultColor
theme('color', '$defaultColor')
text-align: center
button.btn
@ -85,11 +85,11 @@ $height = 20px
bottom: 0
width: 100%
margin-bottom: 0
background: $uiGrayDarker
theme('background', '$uiGrayDarker')
p
font-size: 8pt
color: $defaultColor
theme('color', '$defaultColor')
margin: 0 10px
display: inline-block
@ -107,11 +107,11 @@ $height = 20px
#selectProtocol
text-align: center
.btn-file
.btn-file
position: relative
overflow: hidden
.btn-file input[type=file]
.btn-file input[type=file]
position: absolute
top: 0
right: 0
@ -141,4 +141,4 @@ $height = 20px
padding: 40px
h3
color: $defaultColor
theme('color', '$defaultColor')

View File

@ -1,6 +1,9 @@
@import "{design}/app.styl"
.ruleEntryDialog
theme('background', '$uiGrayDarkest')
theme('border', '1px solid $uiBorderColor')
theme('color', '$textSecondaryColor')
display: none
position: absolute
top: 0
@ -14,12 +17,8 @@
padding: 10px
background-color: rgba(255,255,255,1)
outline: none
opacity: 0.95
border-radius: 8px
border: 1px solid $uiBorderColor
background: $uiGrayDarkest
color: $textSecondaryColor
.dialogContent
text-align: center
@ -44,4 +43,4 @@
color: black
height: 30px
border-radius: 2px
border: 0
border: 0

View File

@ -4,7 +4,7 @@ table.ruleTable
thead
tr
th
color: $defaultColor
theme('color', '$defaultColor')
text-align: center
th:first-child
@ -13,7 +13,7 @@ table.ruleTable
tbody
tr
td
color: $defaultColor
theme('color', '$defaultColor')
text-align: center
.failWarning
@ -26,10 +26,10 @@ table.ruleTable
transition(all 0.1s ease)
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')
td:first-child
text-align: left
@ -37,14 +37,14 @@ table.ruleTable
.addRuleContainer
margin: 10px 0
text-align: center
color: $defaultColor
theme('color', '$defaultColor')
.addRule
cursor: pointer
transition(all 0.1s ease)
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')

View File

@ -1,6 +1,9 @@
@import "{design}/app.styl"
.settingEntryDialog
theme('border', '1px solid $uiBorderColor')
theme('background', '$uiGrayDarkest')
theme('color', '$textSecondaryColor')
display: none
position: absolute
top: 0
@ -13,12 +16,8 @@
margin: auto
padding: 10px
outline: none
opacity: 0.95
border-radius: 8px
border: 1px solid $uiBorderColor
background: $uiGrayDarkest
color: $textSecondaryColor
.dialogContent
text-align: center
@ -43,4 +42,4 @@
color: black
height: 30px
border-radius: 2px
border: 0
border: 0

View File

@ -4,7 +4,7 @@ table.settingsTable
thead
tr
th
color: $defaultColor
theme('color', '$defaultColor')
text-align: center
th:first-child
@ -15,7 +15,7 @@ table.settingsTable
tr
td
color: $defaultColor
theme('color', '$defaultColor')
text-align: center
.editSetting
@ -24,10 +24,10 @@ table.settingsTable
transition(all 0.1s ease)
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')
td:first-child
text-align: left
@ -35,14 +35,14 @@ table.settingsTable
.addSettingContainer
margin: 10px 0
text-align: center
color: $defaultColor
theme('color', '$defaultColor')
.addSetting
cursor: pointer
transition(all 0.1s ease)
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')

View File

@ -14,19 +14,19 @@
h3
label
color: $defaultColor
theme('color', '$defaultColor')
label
margin-right: 10px
width: 25%
input
theme('background-color', '$boxBackgroundColor')Dark
min-width: 50px
width: 40%
border: none
background: $boxBackgroundColorDark
color: $defaultColor
theme('color', '$defaultColor')
text-align: center
.stageEditorSection
margin: 30px 0
margin: 30px 0

View File

@ -8,14 +8,14 @@
padding: 3px
span
color: $defaultColor
theme('color', '$defaultColor')
cursor: pointer
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&.active
color: $activeColor
theme('color', '$activeColor')
.sortable-handle
cursor: move
@ -36,44 +36,44 @@
.deleteStage
cursor: pointer
color: $defaultColor
theme('color', '$defaultColor')
transition(all 0.1s ease)
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')
.addStage
margin: 10px 0
text-align: center
color: $defaultColor
theme('color', '$defaultColor')
#addStage
transition(all 0.1s ease)
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')
.moveStageButtons
margin: 10px 0
text-align: center
a
color: $defaultColor
theme('color', '$defaultColor')
cursor: pointer
text-decoration: none
transition(all 0.1s ease)
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')
&[disabled="true"]
opacity: 0.7

View File

@ -1,6 +1,9 @@
@import "{design}/app.styl"
.textEntryDialog
theme('border', '1px solid $uiBorderColor')
theme('background', '$uiGrayDarkest')
theme('color', '$textSecondaryColor')
display: none
position: absolute
top: 0
@ -13,12 +16,8 @@
margin: auto
padding: 10px
outline: none
opacity: 0.95
border-radius: 8px
border: 1px solid $uiBorderColor
background: $uiGrayDarkest
color: $textSecondaryColor
.dialogContent
margin-bottom: 10px
@ -36,4 +35,4 @@
color: black
height: 30px
border-radius: 2px
border: 0
border: 0

View File

@ -1,5 +1,8 @@
import { Mongo } from 'meteor/mongo';
// Servers describe the DICOM servers configurations
Servers = new Mongo.Collection('servers');
Timepoints = new Mongo.Collection('timepoints');
Studies = new Mongo.Collection('studies');
Measurements = new Mongo.Collection('measurements');
@ -12,6 +15,5 @@ ImageMeasurements = new Mongo.Collection('imageMeasurements');
// panel, and represents items such as data quality,
AdditionalFindings = new Mongo.Collection('additionalFindings');
Servers = new Mongo.Collection('servers');
WorklistSubscriptions = ['studies', 'timepoints'];
Reviewers = new Mongo.Collection('reviewers');

View File

@ -26,8 +26,14 @@ class TimepointApi {
});
}
// Return the prior timepoint
prior() {
const latestDate = this.current().latestDate;
const current = this.current();
if (!current) {
return;
}
const latestDate = current.latestDate;
return this.timepoints.findOne({
latestDate: {
$lt: latestDate
@ -41,9 +47,15 @@ class TimepointApi {
// Return only the current and prior Timepoints
currentAndPrior() {
let timepoints = [this.current()];
const timepoints = [];
const current = this.current();
if (current) {
timepoints.push(current);
}
const prior = this.prior();
if (prior) {
if (current && prior && prior._id !== current._id) {
timepoints.push(prior);
}

View File

@ -0,0 +1,25 @@
import { Template } from 'meteor/templating';
import { ActiveEntry } from 'meteor/clinical:active-entry';
Template.entrySignIn.hooks({
rendered: function () {
// Create Test Drive button dynamically
let btnTestDrive = $('<button/>', {
id: 'btnTestDrive',
text: 'Test Drive',
class: 'btn btn-primary',
style: 'position: absolute; width: 150px; top: 20px; right: 20px; padding-left: 0;',
title: 'Login with Demo User',
click: function () {
// Login with demo user
ActiveEntry.signIn('demo@ohif.org', '12345678aA*');
}
});
const entrySignIn = this.find('#entrySignIn');
$(entrySignIn).append(btnTestDrive);
}
});

View File

@ -15,7 +15,7 @@
key='regionsOfMetastaticDisease'
multiple=true
options=(clone
placeholder='Choose a region...'
placeholder='Choose a region'
allowClear=true
)
}}

View File

@ -22,22 +22,22 @@
display: none
.header
theme('background-color', '$uiGray')
theme('color', '$textPrimaryColor')
height: 33px
line-height: 33px
font-weight: bold
font-size: 14px
padding: 1px 13px 0
color: $textPrimaryColor
background-color: $uiGray
.form-group
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
margin-bottom: 0
padding: 7px 27px 7px 11px
width: 100%
&:not(:last-child)
border-bottom: $uiBorderThickness solid $uiBorderColorDark
theme('border-bottom', '%s solid $uiBorderColorDark' % $uiBorderThickness)
& > .wrapperText
display: block
@ -81,21 +81,21 @@
.buttonSection
cursor: pointer
height: 90px
color: $defaultColor
theme('color', '$defaultColor')
&:hover
color: $hoverColor
theme('color', '$hoverColor')
.svgContainer
svg
fill: $hoverColor
theme('fill', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')
.svgContainer
svg
fill: $activeColor
theme('fill', '$activeColor')
div
float: left
@ -112,4 +112,4 @@
svg
width: 36px
height: 36px
fill: $textPrimaryColor
theme('fill', '$textPrimaryColor')

View File

@ -7,7 +7,14 @@ Template.caseProgress.onCreated(() => {
instance.progressText = new ReactiveVar();
instance.isLocked = new ReactiveVar();
// Get the current timepoint
const current = instance.data.timepointApi.current();
// Stop here if no timepoint was found
if (!current) {
return;
}
if (!current.timepointId) {
console.warn('Case has no timepointId');
return;

View File

@ -13,15 +13,15 @@
margin-left: 22px
h5
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
line-height: 46px
margin: 0
font-size: 15px
font-weight: bold
button.btn
background-color: $activeColor
border: 1px solid $uiBorderColorActive
theme('background-color', '$activeColor')
theme('border', '1px solid $uiBorderColorActive')
color: #000
cursor: pointer
font-size: 15px

View File

@ -25,11 +25,11 @@
transform(translateX(0%))
.sidebar-left
theme('border-right', '%s solid $uiBorderColor' % $uiBorderThickness)
max-width: $studiesSidebarMenuWidth
order: 1
flex: 1
margin-left: - $studiesSidebarMenuWidth
border-right: $uiBorderThickness $uiBorderColor solid
&.sidebar-open
margin-left: 0
@ -60,7 +60,7 @@
max-width: $lesionsSidebarMenuWidth + 270px
&:before
background-color: $uiBorderColor
theme('background-color', '$uiBorderColor')
content: ''
display: block
height: 100%
@ -73,6 +73,6 @@
margin-right: 0
.studiesListedChanger
theme('border-bottom', '%s solid $uiBorderColor' % $uiBorderThickness)
padding: 20px 10px
text-align: center
border-bottom: $uiBorderThickness $uiBorderColor solid

View File

@ -1,9 +0,0 @@
Template.lastLoginModal.helpers({
lastLoginDate: function() {
var userLoginDate = ReactiveMethod.call('getPriorLoginDate');
if (userLoginDate && userLoginDate.priorLoginDate) {
return moment(userLoginDate.priorLoginDate).format("MMMM Do YYYY, HH:mm:ss A");
}
return;
}
});

View File

@ -1,13 +1,17 @@
<template name="lesionTable">
<div id="lesionTableContainer">
<div class="lesionTableLayoutChanger viewerRoundedButtonGroup">
{{>roundedButtonGroup buttonGroupData}}
</div>
<div class="lesionTableTimepointHeaderRow">
{{#each this.timepoints.get}}
{{>lesionTableTimepointHeader}}
{{/each}}
</div>
{{>lesionTableView (clone this)}}
{{#if this.timepoints.get.length}}
<div class="lesionTableLayoutChanger viewerRoundedButtonGroup">
{{>roundedButtonGroup buttonGroupData}}
</div>
<div class="lesionTableTimepointHeaderRow">
{{#each this.timepoints.get}}
{{>lesionTableTimepointHeader}}
{{/each}}
</div>
{{>lesionTableView (clone this)}}
{{else}}
<h3 class="p-x-1 text-center">No associated timepoint</h3>
{{/if}}
</div>
</template>

View File

@ -1,7 +1,7 @@
@import "{design}/app"
#lesionTableContainer
background-color: $primaryBackgroundColor
theme('background-color', '$primaryBackgroundColor')
height: 100%
width: 100%

View File

@ -22,9 +22,9 @@ $borderColor = rgba(77, 99, 110, 0.81)
padding-left: 0
.header
background: $uiGrayDarkest
theme('background', '$uiGrayDarkest')
theme('color', '$textSecondaryColor')
border-bottom: $uiBorderThickness solid $borderColor
color: $textSecondaryColor
font-weight: 300
font-size: 20px
height: 55px
@ -34,12 +34,12 @@ $borderColor = rgba(77, 99, 110, 0.81)
width: 100%
svg.buttonClose
color: $defaultColor
theme('color', '$defaultColor')
cursor: pointer
height: 14px
position: absolute
right: 14px
stroke: $defaultColor
theme('stroke', '$defaultColor')
top: 20px
width: 14px
@ -48,19 +48,19 @@ $borderColor = rgba(77, 99, 110, 0.81)
padding-bottom: 20px
.footer
theme('background', '$uiGrayDarkest')
border-top: $uiBorderThickness solid $borderColor
width: 100%
height: 70px
position: absolute
bottom: 0
background: $uiGrayDarkest
border-top: $uiBorderThickness solid $borderColor
padding: 10px
.toolbarSectionButton
display: inline-block
color: $defaultColor
fill: $defaultColor
stroke: $defaultColor
theme('color', '$defaultColor')
theme('fill', '$defaultColor')
theme('stroke', '$defaultColor')
padding: 0 10px
height: $toolbarHeight
min-width: 30px
@ -85,15 +85,15 @@ $borderColor = rgba(77, 99, 110, 0.81)
height: 21px
&:active, &.active
color: $activeColor
theme('color', '$activeColor')
svg
fill: $activeColor
stroke: $activeColor
theme('fill', '$activeColor')
theme('stroke', '$activeColor')
&:hover
color: $hoverColor
theme('color', '$hoverColor')
svg
fill: $hoverColor
stroke: $hoverColor
theme('fill', '$hoverColor')
theme('stroke', '$hoverColor')

View File

@ -16,7 +16,14 @@ Template.lesionTableHeaderRow.onCreated(() => {
const instance = Template.instance();
instance.maxNumLesions = new ReactiveVar();
// Get the current timepoint
const current = instance.data.timepointApi.current();
// Stop here if no timepoint was found
if (!current) {
return;
}
const timepointType = current.timepointType;
if (!instance.data.currentTimepointId) {

View File

@ -3,10 +3,10 @@
$headerRowHeight = 63px
.lesionTableHeaderRow
background-color: $uiGrayDarker
color: $textSecondaryColor
theme('background-color', '$uiGrayDarker')
theme('color', '$textSecondaryColor')
display: flex
fill: $textSecondaryColor
theme('fill', '$textSecondaryColor')
height: $headerRowHeight
line-height: $headerRowHeight
margin-top: 2px
@ -35,13 +35,13 @@ $headerRowHeight = 63px
max-width: 11px
&:hover
fill: $hoverColor
theme('fill', '$hoverColor')
&:active
fill: $activeColor
theme('fill', '$activeColor')
.type
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
font-size: 22px
font-weight: 300
line-height: $headerRowHeight
@ -49,8 +49,8 @@ $headerRowHeight = 63px
text-align: left
.numberOfLesions
theme('color', '$uiSkyBlue')
float: right
color: $uiSkyBlue
font-weight: 300
font-size: 40px
max-width: 54px
@ -63,7 +63,7 @@ $headerRowHeight = 63px
text-align: right
.maxNumLesions
background-color: $textSecondaryColor
theme('background-color', '$textSecondaryColor')
border-radius: 3px
color: black
display: table
@ -79,5 +79,5 @@ $headerRowHeight = 63px
&.warning
.maxNumLesions
color: $textPrimaryColor
background-color: $uiYellow
theme('background-color', '$uiYellow')
theme('color', '$textPrimaryColor')

View File

@ -10,7 +10,7 @@
width: calc(100% + 6px)
&:before
background-color: $activeColor
theme('background-color', '$activeColor')
border-top-left-radius: 7px
border-bottom-left-radius: 7px
content: ''
@ -30,16 +30,16 @@
opacity: 1
.lesionRowSidebar
color: $activeColor
theme('color', '$activeColor')
.rowOptions
height: 35px
visibility: visible
&.response-status .lesionRowSidebar .response-status-icon
background-color: $defaultColor
theme('background-color', '$defaultColor')
theme('color', '$uiGray')
border-radius: 11px
color: $uiGray
font-size: 12px
font-weight: 700
height: 21px
@ -49,8 +49,8 @@
width: 21px
&.warning .lesionRowSidebar
color: $textPrimaryColor
background-color: $uiYellow
theme('background-color', '$uiYellow')
theme('color', '$textPrimaryColor')
.warning-icon, svg
width: 22px
@ -60,31 +60,31 @@
margin: 7px auto 0
svg
fill: $textPrimaryColor
theme('fill', '$textPrimaryColor')
.rowOptions
background-color: $uiGrayDarker
theme('background-color', '$uiGrayDarker')
height: 0
overflow: hidden
transition(all 0.3s ease)
visibility: hidden
.rowAction
color: $defaultColor
theme('color', '$defaultColor')
cursor: pointer
line-height: 35px
transition(all 0.3s ease)
&:hover, &:active
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
svg
fill: $textPrimaryColor
stroke: $textPrimaryColor
theme('fill', '$textPrimaryColor')
theme('stroke', '$textPrimaryColor')
svg
fill: $defaultColor
stroke: $defaultColor
theme('fill', '$defaultColor')
theme('stroke', '$defaultColor')
transition(all 0.3s ease)
&.edit-icon
@ -96,8 +96,8 @@
height: 11px
.lesionRowSidebar
background: $uiGray
color: $textSecondaryColor
theme('background', '$uiGray')
theme('color', '$textSecondaryColor')
cursor: pointer
flex: 1
max-width: 30px
@ -120,7 +120,7 @@
.location
font-weight: 400
font-size: 14px
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
height: 30px
line-height: 30px
margin-left: -2px

View File

@ -1,6 +1,6 @@
@import "{design}/app"
.lesionTableTimepointCell
border-left: $uiBorderThickness $uiBorderColor solid
color: $textPrimaryColor
theme('border-left', '%s solid $uiBorderColor' % $uiBorderThickness)
theme('color', '$textPrimaryColor')
padding: 0 10px

View File

@ -5,18 +5,18 @@
justify-content: space-around
div
border-left: $uiBorderColor $uiBorderThickness solid
theme('border-left', '%s solid $uiBorderColor' % $uiBorderThickness)
font-weight: 400
padding-left: 12px
text-align: left
.timepointName
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
font-size: 12px
line-height: 12px
.timepointDate
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
font-size: 14px
line-height: 20px
padding-bottom: 6px

View File

@ -38,4 +38,5 @@
{{ >lastLoginModal }}
{{ >studyContextMenu }}
{{ >progressDialog}}
{{ >viewSeriesDetailsModal }}
</template>

View File

@ -9,8 +9,8 @@ $expandedHeight = 160px
.topBar
height: $topBarHeight
color: $textPrimaryColor
background-color: $primaryBackgroundColor
theme('color', '$textPrimaryColor')
theme('background-color', '$primaryBackgroundColor')
transition(all 0.5s ease)
.brandSection
@ -26,14 +26,14 @@ $expandedHeight = 160px
display: inline-block
font-family: $logoFontFamily
font-weight: $logoFontWeight
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
line-height: 30px
.studyListLinkSection
border-left: $uiBorderThickness solid $uiBorderColor;
theme('border-left', '%s solid $uiBorderColor' % $uiBorderThickness)
.btn
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
cursor: pointer
font-size: 13px
font-weight: 500
@ -41,10 +41,10 @@ $expandedHeight = 160px
margin-top: 3px
&:hover
color: $hoverColor
theme('color', '$hoverColor')
&:active
color: $activeColor
theme('color', '$activeColor')
&.studyList
background-color: rgba(21, 25, 30, 0.7)
@ -75,5 +75,5 @@ $expandedHeight = 160px
.tab-pane
.viewerContainer
.loadingTextDiv
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
font-size: 30px

View File

@ -24,7 +24,10 @@
Remove Association
</a>
<hr/>
<a class="disabled">View Series Details</a>
<a id="viewSeriesDetails" type="button"
title="View Series Details">
View Series Details
</a>
<a class="disabled">Anonymize</a>
<a class="disabled">Send</a>
<hr/>

View File

@ -1,8 +1,9 @@
<template name="measureFlow">
<div class="measure-flow {{instance.state.get}}" style="position:fixed; top:200px; left:400px">
<div class="measure-flow {{#if this.treeColumns}}tree-columns{{/if}} {{instance.state.get}}" style="position:fixed; top:200px; left:400px">
<button class="btn-add">Add label</button>
{{#if eq instance.state.get 'selected'}}
<div class="tree-leaf">
<i class="icon-check"></i>
<span>{{instance.value.label}}</span>
{{#if instance.descriptionEdit.get}}
<div class="description clearfix">

View File

@ -55,6 +55,7 @@ Template.measureFlow.onCreated(() => {
}];
const items = [
'Abdomen/Chest Wall',
'Adrenal',
'Bladder',
'Bone',
@ -68,9 +69,10 @@ Template.measureFlow.onCreated(() => {
'Liver',
'Lung',
'Lymph Node',
'Mediastinum/Hilum',
'Muscle',
'Neck',
'Other: Soft Tissue',
'Other Soft Tissue',
'Ovary',
'Pancreas',
'Pelvis',
@ -90,6 +92,25 @@ Template.measureFlow.onCreated(() => {
value: item
});
});
const commonItems = [
'Abdomen/Chest Wall',
'Lung',
'Lymph Node',
'Liver',
'Mediastinum/Hilum',
'Pelvis',
'Peritoneum/Omentum',
'Retroperitoneum'
];
instance.commonItems = [];
_.each(commonItems, item => {
instance.commonItems.push({
label: item,
value: item
});
});
});
Template.measureFlow.onRendered(() => {
@ -116,9 +137,12 @@ Template.measureFlow.events({
const data = {
key: 'label',
items: instance.items,
commonItems: instance.commonItems,
hideCommon: instance.data.hideCommon,
label: 'Assign label',
searchPlaceholder: 'Search labels',
storageKey: 'measureLabelCommon',
treeColumns: instance.data.treeColumns,
position
};
@ -206,14 +230,14 @@ Template.measureFlow.events({
// Change the measure flow state to selected
instance.state.set('selected');
// Get the clicked label window offset
const labelOffset = $label.offset();
// Wait for the DOM re-rendering
Tracker.afterFlush(() => {
// Get the measure flow div
const $measureFlow = instance.$('.measure-flow');
// Get the clicked label window offset
const labelOffset = $label.offset();
// Subtract the label box shadow height from the label's position
labelOffset.top -= 10;

View File

@ -10,10 +10,10 @@
opacity: 0
.btn-add
background-color: $activeColor
border: 2px solid $uiBorderColor
theme('color', '$textColorActive')
theme('background-color', '$activeColor')
theme('border', '2px solid $uiBorderColor')
border-radius: 14px
color: $textColorActive
cursor: pointer
font-weight: bold
height: 24px
@ -26,14 +26,13 @@
transition(opacity 0.3s ease)
white-space: nowrap
// / {selector()}
// color: $themes['tide']['uiYellow'] !important
.select-tree-root>.tree-content
width: 213px
&>.tree-options
margin-right: -17px
max-height: 250px
overflow-x: hidden
overflow-y: scroll
position: relative
z-index: 2
@ -43,22 +42,11 @@
&>.tree-leaf
position: relative
&:before
animateZoomIn()
background-color: $activeColor
border-radius: 20px
color: $uiGray
content: '\f00c'
display: block
font-family: FontAwesome
font-size: 24px
height: 40px
.icon-check
theme('background-color', '$activeColor')
left: -46px
line-height: 40px
position: absolute
text-align: center
top: 3px
width: 40px
span
background: white
@ -82,10 +70,10 @@
animateFadeOut()
button
theme('border', '1px solid $uiBorderColor')
theme('color', '$textPrimaryColor')
background-color: transparent
border-radius: 16px
border: 1px solid $uiBorderColor
color: $textPrimaryColor
font-weight: normal
height: 31px
line-height: 31px
@ -120,3 +108,80 @@
span
display: block
&.tree-columns
.select-tree-root
&.interacted .select-tree-common .content
animation-name: none
&.selected .select-tree-common .content
animation-name: selectTreeCommonCloseLeft
.tree-search
input, i
display: none
.select-tree-common
padding-left: 0
padding-right: 6px
right: auto
left: 0
width: 33.333333333333%
&>.tree-content
transform-origin(0% 50%)
width: 426px
.select-tree-common .content
right: -100%
&.started .select-tree-common .content
right: 6px
&.interacted .select-tree-common .content
animation-name: selectTreeCommonCloseRight
.tree-inputs>label
float: left
width: 50%
@keyframes selectTreeCommonCloseRight {
from {
height: 37px
display: table
right: 6px
opacity: 1
visibility: visible
}
to {
height: 100%
right: -100%
opacity: 0
visibility: hidden
width: calc(100% - 6px)
}
}
.icon-check
background-color: white
border-radius: 20px
display: block
height: 40px
overflow: hidden
position: relative
width: 40px
&:before
border: 2px solid black
border-left-width: 0
border-top-width: 0
content: ''
display: block
height: 18px
left: 15px
position: absolute
top: 9px
transform(rotate(45deg))
width: 9px

View File

@ -11,12 +11,12 @@ $circleSize = 46px
#svg circle
stroke-dashoffset: 0
stroke: $uiBorderColorDark
theme('stroke', '$uiBorderColorDark')
stroke-width: 3px
vendorize(transition, stroke-dashoffset 1s linear)
#svg #bar
stroke: $activeColor
theme('stroke', '$activeColor')
vendorize(transform, rotate(270deg))
vendorize(transform-origin, center center)
@ -28,14 +28,14 @@ $circleSize = 46px
font-weight: 700
font-size: 17px
text-align: center
color: $activeColor
theme('color', '$activeColor')
top: 0
svg
fill: $activeColor
theme('fill', '$activeColor')
left: 50%
position: absolute
stroke: $activeColor
theme('stroke', '$activeColor')
top: 50%
transform(translateX(-50%) translateY(-50%))

View File

@ -1,30 +1,27 @@
<template name="serverInformationDicomWeb">
<div class="row">
{{>serverInformationFormField name="wadoUriRoot" label="WADO URI root"}}
{{>serverInformationFormField name="wadoUriRootNOTE" label="WADO URI root note"}}
{{>serverInformationFormField name="wadoRoot" label="WADO root"}}
<div class="col-lg-6">
<div class="form-group">
<label class="wrapper">
<strong>Image rendering</strong>
<select name="imageRendering" class="form-control">
<option>Select</option>
<option value="wadouri">WADO URI</option>
<option value="orthanc">ORTHANC</option>
</select>
</label>
</div>
{{>inputText labelClass='form-group' key='wadoUriRoot'}}
</div>
<div class="col-lg-6">
{{>inputText labelClass='form-group' key='wadoUriRootNOTE'}}
</div>
<div class="col-lg-6">
{{>inputText labelClass='form-group' key='wadoRoot'}}
</div>
<div class="col-lg-6">
{{>inputSelect labelClass='form-group' key='imageRendering' hideSearch=true
options=(clone placeholder='Select an option')}}
</div>
<div class="col-lg-6">
{{>inputText labelClass='form-group' key='qidoRoot'}}
</div>
{{>serverInformationFormField name="qidoRoot" label="QIDO root"}}
<div class="col-lg-6">
<strong>QIDO options</strong>
<div class="form-group">
<div class="form-control">
<div class="checkbox">
<label>
<input type="checkbox" name="qidoSupportsIncludeField">
<span>QIDO supports including fields</span>
</label>
{{>inputCheckbox key='qidoSupportsIncludeField'}}
</div>
</div>
</div>
@ -32,32 +29,25 @@
</div>
<hr>
<h4>Request options</h4>
<div class="row">
{{>serverInformationFormField name="requestOptions.auth" label="Authentication"}}
{{#group class='row' key='requestOptions'}}
<div class="col-lg-6">
{{>inputText labelClass='form-group' key='auth'}}
</div>
<div class="col-lg-6">
<strong>Logging options</strong>
<div class="form-group">
<div class="form-control clearfix">
<div class="checkbox pull-left">
<label>
<input type="checkbox" name="requestOptions.logRequests">
<span>Requests</span>
</label>
{{>inputCheckbox key='logRequests'}}
</div>
<div class="checkbox pull-left">
<label>
<input type="checkbox" name="requestOptions.logResponses">
<span>Responses</span>
</label>
{{>inputCheckbox key='logResponses'}}
</div>
<div class="checkbox pull-left">
<label>
<input type="checkbox" name="requestOptions.logTiming">
<span>Timing</span>
</label>
{{>inputCheckbox key='logTiming'}}
</div>
</div>
</div>
</div>
</div>
{{/group}}
</template>

View File

@ -1,10 +1,10 @@
Template.serverInformationDicomWeb.onRendered(function() {
var instance = Template.instance();
Template.serverInformationDicomWeb.onRendered(() => {
const instance = Template.instance();
instance.autorun(function() {
var mode = instance.data.mode.get();
const mode = instance.data.mode.get();
if (mode === 'edit') {
var data = instance.data.currentItem.get();
FormUtils.setFormData(instance.data.$form, data);
const data = instance.data.currentItem.get();
instance.data.form.value(data);
}
});
});

View File

@ -1,43 +1,45 @@
<template name="serverInformationDimse">
<div class="panel panel-default panel-body">
<div class="clearfix">
<h4 class="pull-left">Peer list</h4>
<h4 class="pull-left">Peer List</h4>
<button class="btn btn-primary pull-right js-new-peer">New peer</button>
</div>
<hr>
<div class="row">
{{#group class='row' key='peers' arrayValues=true}}
{{#each peer in instance.peers.get}}
<div class="col-lg-6">
{{#group class='col-lg-6'}}
<div class="panel panel-default panel-body">
<div class="row">
{{>serverInformationFormField name=(concat "peers[]." @index ".aeTitle") label="AE title"}}
{{>serverInformationFormField name=(concat "peers[]." @index ".hostAE") label="AE host"}}
{{>serverInformationFormField name=(concat "peers[]." @index ".host") label="Host"}}
{{>serverInformationFormField name=(concat "peers[]." @index ".port") label="Port"}}
<div class="col-lg-6">
{{>inputText class='form-control' key='aeTitle'}}
</div>
<div class="col-lg-6">
{{>inputText class='form-control' key='hostAE'}}
</div>
<div class="col-lg-6">
{{>inputText class='form-control' key='host'}}
</div>
<div class="col-lg-6">
{{>inputText class='form-control' key='port'}}
</div>
<div class="col-lg-12">
<strong>Settings</strong>
<div class="form-group">
<div class="form-control clearfix">
<div class="checkbox pull-left">
<label>
<input type="checkbox" name="peers[].{{@index}}.default">
<span>Default</span>
</label>
{{>inputCheckbox key='default'}}
</div>
<div class="checkbox pull-left">
<label>
<input type="checkbox" name="peers[].{{@index}}.server">
<span>Server</span>
</label>
{{>inputCheckbox key='server'}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{/group}}
{{/each}}
</div>
{{/group}}
</div>
<hr>
</template>

View File

@ -1,29 +1,29 @@
Template.serverInformationDimse.onCreated(function() {
var instance = Template.instance();
Template.serverInformationDimse.onCreated(() => {
const instance = Template.instance();
instance.peers = new ReactiveVar([]);
instance.autorun(function() {
var currentItem = instance.data.currentItem.get();
instance.autorun(() => {
const currentItem = instance.data.currentItem.get();
if (currentItem) {
instance.peers.set(currentItem.peers || []);
}
});
});
Template.serverInformationDimse.onRendered(function() {
var instance = Template.instance();
instance.autorun(function() {
var mode = instance.data.mode.get();
Template.serverInformationDimse.onRendered(() => {
const instance = Template.instance();
instance.autorun(() => {
const mode = instance.data.mode.get();
if (mode === 'edit') {
var data = instance.data.currentItem.get();
FormUtils.setFormData(instance.data.$form, data);
const data = instance.data.currentItem.get();
instance.data.form.value(data);
}
});
});
Template.serverInformationDimse.events({
'click .js-new-peer': function(event, instance) {
'click .js-new-peer'(event, instance) {
event.preventDefault();
var peers = instance.peers.get();
const peers = instance.peers.get();
peers.push({});
instance.peers.set(peers);
}

View File

@ -1,33 +1,21 @@
<template name="serverInformationForm">
<form class="server-information-form">
<input type="hidden" name="_id">
{{#form class='server-information-form' schema=instance.currentSchema}}
{{>inputHidden key='_id'}}
<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label class="wrapper">
<strong>Server Name</strong>
<input type="text" name="name" class="form-control">
</label>
</div>
{{>inputText labelClass='form-group' key='name'}}
</div>
<div class="col-lg-6">
<div class="form-group">
<label class="wrapper">
<strong>Server Type</strong>
<select name="type" class="form-control js-server-type">
<option>Select</option>
<option value="dicomWeb">DICOM Web</option>
<option value="dimse">DIMSE</option>
</select>
</label>
</div>
{{>inputSelect labelClass='form-group' key='type'
hideSearch=true disabled=(eq this.mode.get 'edit')
options=(clone placeholder='Choose a server type')}}
</div>
</div>
<hr>
{{#if eq this.serverType.get "dicomWeb"}}
{{#if eq this.serverType.get 'dicomWeb'}}
{{>serverInformationDicomWeb this}}
{{/if}}
{{#if eq this.serverType.get "dimse"}}
{{#if eq this.serverType.get 'dimse'}}
{{>serverInformationDimse this}}
{{/if}}
{{#if this.serverType.get}}
@ -35,5 +23,5 @@
<input type="submit" class="btn btn-primary" value="Save">
</div>
{{/if}}
</form>
{{/form}}
</template>

View File

@ -1,24 +1,68 @@
Template.serverInformationForm.onRendered(function() {
var instance = Template.instance();
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { Tracker } from 'meteor/tracker';
import { DICOMWebServer as dicomSchema } from 'meteor/worklist/both/schema';
import { DIMSEServer as dimseSchema } from 'meteor/worklist/both/schema';
instance.data.$form = instance.$('form');
instance.autorun(function() {
var mode = instance.data.mode.get();
Template.serverInformationForm.onCreated(() => {
const instance = Template.instance();
instance.currentSchema = new ReactiveVar(dicomSchema);
});
Template.serverInformationForm.onRendered(() => {
const instance = Template.instance();
instance.data.$form = instance.$('form:first');
instance.data.form = instance.data.$form.data('component');
// Handle the server type
instance.autorun(() => {
// Get the server type component
const typeComponent = instance.$('[data-key=type] :input').data('component');
// Run this computation every time the user change the server type
typeComponent.depend();
// Get the current server type value
const type = typeComponent.value();
// Set the serverType reactive value
instance.data.serverType.set(type);
// Change the schema based on the selected server type
if (type === 'dimse') {
instance.currentSchema.set(dimseSchema);
} else {
instance.currentSchema.set(dicomSchema);
}
});
// Handle the form mode (edit or add)
instance.autorun(() => {
const mode = instance.data.mode.get();
// Check if it is on edit mode and load the saved data
if (mode === 'edit') {
var data = instance.data.currentItem.get();
FormUtils.setFormData(instance.data.$form, data);
const data = instance.data.currentItem.get();
instance.data.form.value(data);
}
});
});
Template.serverInformationForm.events({
'change .js-server-type': function(event, instance) {
var value = $(event.currentTarget).val();
instance.data.serverType.set(value);
},
submit: function(event, instance) {
submit(event, instance) {
event.preventDefault();
var formData = FormUtils.getFormData(instance.data.$form);
// Stop here if the form validation fails
if (!instance.data.form.validate()) {
return;
}
// Get the current form data
const formData = instance.data.form.value();
// Call the save method
Meteor.call('serverSave', formData, function(error) {
if (error) {
// TODO: check for errors: not-authorized, data-write

View File

@ -15,7 +15,7 @@ $seriesSpacing = 2px
transform(translateX(-50%))
&.left
border-right: 1px solid $uiBorderColor
theme('border-right', '1px solid $uiBorderColor')
right: 50%
&.right
@ -26,7 +26,7 @@ $seriesSpacing = 2px
z-index: 3
&:before
background-color: $primaryBackgroundColor
theme('background-color', '$primaryBackgroundColor')
bottom: 0
content: ''
left: 0
@ -98,10 +98,10 @@ $seriesSpacing = 2px
.studyTimepointBrowser
background-color: transparent
.studyTimepointStudy.active .studyModality
box-shadow: inset 0 0 0 3px $activeColor
theme('box-shadow', 'inset 0 0 0 3px $activeColor')
.studyBox
background-color: $uiGrayDark
border: solid 1px $activeColor
theme('background-color', '$uiGrayDark')
theme('border', 'solid 1px $activeColor')
border-radius: 11px
display: block
height: $switchSize
@ -113,7 +113,7 @@ $seriesSpacing = 2px
width: $switchSize + $seriesSpacing
.seriesItem
background-color: $boxBackgroundColorDark
theme('background-color', '$boxBackgroundColor')Dark
border-radius: 3px
height: 15px
margin: $seriesSpacing
@ -121,17 +121,17 @@ $seriesSpacing = 2px
&.count
background-color: transparent
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
font-size: 12px
font-weight: 500
line-height: 17px
text-align: center
&.active
background-color: $activeColor
theme('background-color', '$activeColor')
.label
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
display: block
font-size: 12px
font-weight: 300

View File

@ -3,15 +3,15 @@
$timepointButtonHeight = 55px
.studyTimepointBrowser
background-color: $primaryBackgroundColor
theme('background-color', '$primaryBackgroundColor')
float: left
height: 100%
position: relative
width: 100%
.timepointButtonContainer
background-color: $primaryBackgroundColor
border-bottom: 1px solid $uiBorderColor
theme('background-color', '$primaryBackgroundColor')
theme('border-bottom', '1px solid $uiBorderColor')
display: none
height: $timepointButtonHeight
left: 10px
@ -38,7 +38,7 @@ $timepointButtonHeight = 55px
transform: rotateX(180deg)
.timepointFollowupTitle
font-size: 14px
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
text-transform: uppercase
.timepointHeader
@ -50,7 +50,7 @@ $timepointButtonHeight = 55px
.timepointFollowupTitle
.timepointDate
.timepointModalities
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
transition($sidebarTransition)
&:hover
@ -59,7 +59,7 @@ $timepointButtonHeight = 55px
.expandIcon i
.timepointDate
.timepointModalities
color: $textPrimaryColor !important
theme('color', '$textPrimaryColor') !important
.timepointDetails
.timepointModalities
@ -70,10 +70,10 @@ $timepointButtonHeight = 55px
overflow: hidden
.expandIcon i
color: $defaultColor
theme('color', '$defaultColor')
.timepointFollowupTitle
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
padding-top: 2px
.studyTimepointScrollArea
@ -91,7 +91,7 @@ $timepointButtonHeight = 55px
margin-right: 16px
.studySidebarTimepoint .thumbnailEntry.active:before
background-color: $activeColor
theme('background-color', '$activeColor')
border-top-right-radius: 7px
border-bottom-right-radius: 7px
content: ''

View File

@ -3,8 +3,6 @@
$boxBorderColor = transparent
$boxHoverBackgroundColor = #14191E
$boxHoverBorderColor = #2d4660
$boxActiveBackgroundColor = $activeColor
$boxActiveTextColor = $primaryBackgroundColor
$boxActiveBorderColor = #131D29
$boxWidth = 50px
$borderThickness = 2px
@ -41,11 +39,11 @@ $spacerY = 12px
&.active
.studyModality
.studyModalityBox
color: $boxActiveTextColor
theme('color', '$primaryBackgroundColor')
&
&:before
&:after
background-color: $boxActiveBackgroundColor
theme('background-color', '$activeColor')
border-color: $boxActiveBorderColor
&:not(.active) .studyTimepointThumbnails
@ -69,10 +67,10 @@ $spacerY = 12px
border-color: $boxHoverBorderColor
&.additional
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
&.additional
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
font-size: 16px
font-weight: normal
height: 91px
@ -89,13 +87,13 @@ $spacerY = 12px
top: $spacerY
.studyDate
margin-top: 8px
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
.studyDescription
margin-top: 8px
color: $textPrimaryColor
theme('color', '$textPrimaryColor')
.studyModalityBox
color: $textSecondaryColor
theme('color', '$textSecondaryColor')
line-height: $boxWidth
margin-left: $nestingMargin * 2
margin-top: $nestingMargin * 2
@ -109,8 +107,8 @@ $spacerY = 12px
&
&:before
&:after
background-color: $boxBackgroundColor
border: $borderThickness solid $primaryBackgroundColor
theme('border', '$borderThickness solid $primaryBackgroundColor')
theme('background-color', '$boxBackgroundColor')
border-radius: $boxBorderRadius
height: $boxWidth + ($borderThickness * 2)
transition($sidebarTransition)

View File

@ -53,7 +53,10 @@ Template.toolbarSection.helpers({
},
toolbarButtons() {
var buttonData = [];
// Check if the measure tools shall be disabled
const isMesureDisabled = !Template.instance().data.timepointApi.currentTimepointId;
const buttonData = [];
buttonData.push({
id: 'zoom',
title: 'Zoom',
@ -86,14 +89,16 @@ Template.toolbarSection.helpers({
id: 'bidirectional',
title: 'Target',
classes: 'imageViewerTool rm-l-3',
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-target'
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-target',
disabled: isMesureDisabled
});
buttonData.push({
id: 'nonTarget',
title: 'Non-Target',
classes: 'imageViewerTool toolbarSectionButton',
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-non-target'
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-non-target',
disabled: isMesureDisabled
});
buttonData.push({
@ -107,28 +112,33 @@ Template.toolbarSection.helpers({
},
extraToolbarButtons() {
let buttonData = [];
// Check if the measure tools shall be disabled
const isMesureDisabled = !Template.instance().data.timepointApi.currentTimepointId;
const buttonData = [];
// TODO: Get real icons for CR / UN / EX
buttonData.push({
id: 'crTool',
title: 'CR Tool',
classes: 'imageViewerTool toolbarSectionButton',
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp'
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp',
disabled: isMesureDisabled
});
buttonData.push({
id: 'unTool',
title: 'UN Tool',
classes: 'imageViewerTool toolbarSectionButton',
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp'
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp',
disabled: isMesureDisabled
});
buttonData.push({
id: 'exTool',
title: 'EX Tool',
classes: 'imageViewerTool toolbarSectionButton',
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp'
svgLink: '/packages/viewerbase/assets/icons.svg#icon-tools-measure-temp',
disabled: isMesureDisabled
});
return buttonData;

View File

@ -1,7 +1,7 @@
@import "{design}/app"
.toolbarSection
border-bottom: $uiBorderColor $uiBorderThickness solid
theme('border-bottom', '%s solid $uiBorderColor' % $uiBorderThickness)
flex: 0 0 auto
height: $toolbarHeight
overflow: hidden
@ -14,11 +14,11 @@
height: $toolbarHeight + $toolbarDrawerHeight
.toolbarSectionEntry
color: $defaultColor
theme('color', '$defaultColor')
cursor: pointer
display: inline-block
fill: $defaultColor
theme('fill', '$defaultColor')
margin-top: 3px
min-width: 30px
stroke: $defaultColor
text-align: center
theme('stroke', '$defaultColor')
text-align: center

View File

@ -1,3 +1,10 @@
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { Session } from 'meteor/session';
// Display the last login modal as default
Session.setDefault('displayLastLoginModal', true);
Template.userAccountMenu.helpers({
name: function() {
var nameSplit = Meteor.user().profile.fullName.split(' ');
@ -29,68 +36,87 @@ Template.userAccountMenu.events({
$('#serverInformationModal').modal('show');
},
'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;
Template.userAccountMenu.onCreated(function userAccountMenuCreated() {
const instance = Template.instance();
this.autorun(function() {
// Hook login/logout
var user = Meteor.user();
if (!user) {
// Create reactive last login date
instance.lastLoginDate = new ReactiveVar();
// We need oldUser to remove Reviewers if the user logged out
let oldUser;
// Get last login date
Meteor.call('getPriorLoginDate', function(error, lastLoginDate){
if (error) {
console.log(error);
return;
}
var newUserId = user._id;
// Format the last login date
const formattedLastLoginDate = moment(lastLoginDate).format("MMMM Do YYYY, HH:mm:ss A");
if (oldUserId === null && newUserId) {
Session.set('showLastLoginModal', true);
instance.lastLoginDate.set(formattedLastLoginDate);
// 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);
instance.autorun(function() {
const lastLoginDate = instance.lastLoginDate.get();
if (!lastLoginDate) {
return;
}
oldUserId = Meteor.userId();
// Hook login/logout
var user = Meteor.user();
if (!user) {
// Display last login modal for the next login
Session.setPersistent('displayLastLoginModal', true);
if (oldUser) {
// Log Signout
// TODO: eventType for logout is not defined
// HipaaLogger.logEvent({
// eventType: 'logout',
// userId: oldUser._id,
// userName: oldUser.profile.fullName
// });
// Remove the user by oldUserId from Reviewers
Meteor.call('removeUserFromReviewers', oldUser._id);
}
return;
}
// Set oldUser
oldUser = user;
// Trigger last login date popup
if (Session.get('showLastLoginModal')) {
Modal.show('lastLoginModal');
lastLoginModalInterval = Meteor.setInterval(function() {
Modal.hide('lastLoginModal');
Session.set('showLastLoginModal', null);
}, 3000);
if (!Session.get('displayLastLoginModal')) {
return;
}
// Displaye the modal
Modal.show('lastLoginModal', {
lastLoginDate: lastLoginDate
});
// Hide the modal after 5sec
Meteor.setTimeout(() => {
Modal.hide('lastLoginModal');
Session.setPersistent('displayLastLoginModal', false);
}, 5000);
// Log signin
HipaaLogger.logEvent({
eventType: 'init',
userId: user._id,
userName: user.profile.fullName
});
});
});

View File

@ -5,7 +5,7 @@
height: 100%
.dropdown-toggle
color: $defaultColor
theme('color', '$defaultColor')
cursor: pointer
font-size: 13px
font-weight: 400
@ -13,19 +13,19 @@
text-decoration: none
.menuButton
border-left: solid 1px $defaultColor
theme('border-left', 'solid 1px $defaultColor')
display: inline-block
height: 18px
padding: 0 8px
.fa
color: $defaultColor
theme('color', '$defaultColor')
font-size: 17px
line-height: 18px
.caret-down
theme('color', '$uiLightGray')
margin: 0 4px 4px 2px
color: $uiLightGray
ul.dropdown-menu
li

View File

@ -19,4 +19,4 @@
&:not(:last-child)
.removable
.imageViewerViewport
border-right: $uiBorderThickness solid $uiBorderColor
theme('border-right', '%s solid $uiBorderColor' % $uiBorderThickness)

View File

@ -1,105 +0,0 @@
class Form {
// Identify the element's type and get its value
static getElementValue($element) {
var type = $element.attr('type');
var value;
switch (type) {
case 'checkbox':
value = $element.is(':checked');
break;
default:
value = $element.val();
}
return value;
};
// Identify the element's type and get its value
static setElementValue($element, value) {
if (!$element.length) {
return;
}
var type = $element.attr('type');
switch (type) {
case 'checkbox':
$element.prop('checked', !!value);
break;
default:
$element.val(value);
}
$element.trigger('change');
};
// Transforms a shallow object with keys separated by "." into a nested object
static getNestedObject(shallowObject) {
var nestedObject = {};
for (var key in shallowObject) {
var value = shallowObject[key];
var propertyArray = key.split('.');
var currentObject = nestedObject;
while (propertyArray.length) {
var currentProperty = propertyArray.shift();
if (!propertyArray.length) {
currentObject[currentProperty] = value;
} else {
if (!currentObject[currentProperty]) {
currentObject[currentProperty] = {};
}
currentObject = currentObject[currentProperty];
}
}
}
return nestedObject;
};
// Transforms a nested object into a shallowObject merging its keys with "." character
static getShallowObject(nestedObject) {
var shallowObject = {};
var putValues = function(baseKey, nestedObject, resultObject) {
for (var key in nestedObject) {
var currentKey = baseKey ? baseKey + '.' + key : key;
var currentValue = nestedObject[key];
if (typeof currentValue === 'object') {
if (currentValue instanceof Array) {
currentKey += '[]';
}
putValues(currentKey, currentValue, resultObject);
} else {
resultObject[currentKey] = currentValue;
}
}
};
putValues('', nestedObject, shallowObject);
return shallowObject;
}
// Gets the nested data for the given form
static getFormData($form) {
var data = {};
$form.find(':input[name]').each((index, element) => {
var $element = $(element);
var value = this.getElementValue($element);
var name = $element.attr('name');
data[name] = value;
});
return this.getNestedObject(data);
}
// Sets the nested data in the given form
static setFormData($form, data) {
var shallowData = this.getShallowObject(data);
for (var key in shallowData) {
var value = shallowData[key];
var $element = $form.find(':input[name="' + key + '"]');
this.setElementValue($element, value);
}
}
}
FormUtils = Form;

View File

@ -58,9 +58,6 @@ Package.onUse(function(api) {
api.addFiles('client/tools.js', 'client');
// Utility classes
api.addFiles('client/utils/form.js', 'client');
// UI Components
api.addFiles('client/components/viewer/viewer.html', 'client');
api.addFiles('client/components/viewer/viewer.styl', 'client');
@ -101,6 +98,8 @@ Package.onUse(function(api) {
api.addFiles('client/components/activeEntry/activeEntry.styl', 'client');
api.addFiles('client/components/activeEntry/activeEntry.js', 'client');
api.addFiles('client/components/activeEntry/activeEntrySignIn/activeEntrySignIn.js', 'client');
api.addFiles('client/components/hipaaLogPage/hipaaLogPage.styl', 'client');
api.addFiles('client/components/hipaaLogPage/hipaaLogPage.js', 'client');
@ -197,7 +196,6 @@ Package.onUse(function(api) {
api.addFiles('client/components/confirmRemoveTimepointAssociation/confirmRemoveTimepointAssociation.js', 'client');
api.addFiles('client/components/lastLoginModal/lastLoginModal.html', 'client');
api.addFiles('client/components/lastLoginModal/lastLoginModal.js', 'client');
api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.html', 'client');
api.addFiles('client/components/confirmDeleteDialog/confirmDeleteDialog.styl', 'client');
@ -208,10 +206,11 @@ Package.onUse(function(api) {
api.addFiles('client/components/emailVerification/emailVerification.js', 'client');
// Server functions
api.addFiles('server/servers.js', 'server');
api.addFiles('server/publications.js', 'server');
api.addFiles('server/servers.js', 'server');
api.addFiles('server/methods.js', [ 'server' ]);
api.addFiles('server/reviewers.js', [ 'server' ]);
api.addFiles('server/createDemoUser.js', [ 'server' ]);
// Both client and server functions
api.addFiles('both/collections.js', [ 'client', 'server' ]);

View File

@ -0,0 +1,39 @@
import { Accounts } from 'meteor/accounts-base';
Meteor.startup(function() {
const options = {
email: 'demo@ohif.org',
password: '12345678aA*',
profile: {
fullName: 'Demo User'
}
};
createDemoUser = function() {
const user = Meteor.users.findOne({
emails: {
$elemMatch: {
address: 'demo@ohif.org'
}
}
});
if (user) {
return;
}
console.log('create user!');
// Create user
const userId = Accounts.createUser(options);
if (!userId) {
console.log('Demo user cannot be created');
return;
}
};
// Create demo user
createDemoUser();
});

View File

@ -85,7 +85,13 @@ Meteor.methods({
},
getPriorLoginDate: function() {
return Meteor.users.findOne({_id: Meteor.userId()}, {fields: {priorLoginDate: 1}});
return Meteor.users.findOne({
_id: Meteor.userId()
}, {
fields: {
priorLoginDate: 1
}
}).priorLoginDate;
}
});

View File

@ -69,4 +69,4 @@ Meteor.methods({
serverSave: serverSettings => ServersControl.save(serverSettings),
serverSetActive: serverId => ServersControl.setActive(serverId),
serverRemove: serverId => ServersControl.remove(serverId)
});
});

View File

@ -0,0 +1 @@
import './schema.js';

View File

@ -0,0 +1,34 @@
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
/*
Extend the available options on schema definitions:
* valuesLabels: Used in conjunction with allowedValues to define the text
label for each value (used on forms)
* textOptional: Used to allow empty strings
*/
SimpleSchema.extendOptions({
valuesLabels: Match.Optional([String]),
textOptional: Match.Optional(Boolean)
});
// Add default required validation for empty strings which can be bypassed
// using textOptional=true definition
SimpleSchema.addValidator(function() {
if (
this.definition.optional !== true &&
this.definition.textOptional !== true &&
this.value === ''
) {
return 'required';
}
});
// Including [label] for some messages
SimpleSchema.messages({
maxCount: '[label] can not have more than [maxCount] values',
minCount: '[label] must have at least [minCount] values',
notAllowed: '[label] has an invalid value: "[value]"'
});

View File

@ -10,6 +10,7 @@ import './mixins/formItem.js';
import './mixins/group.js';
import './mixins/groupRadio.js';
import './mixins/input.js';
import './mixins/checkbox.js';
import './mixins/schemaData.js';
import './mixins/select.js';
import './mixins/select2.js';

View File

@ -0,0 +1,26 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
/*
* inputCheckbox: controls a checkbox input
*/
OHIF.mixins.checkbox = new OHIF.Mixin({
dependencies: 'input',
composition: {
onCreated() {
const instance = Template.instance();
const component = instance.component;
// Get or set the checked state using jQuery's prop method
component.value = value => {
const isGet = _.isUndefined(value);
if (isGet) {
return component.parseData(component.$element.is(':checked'));
}
component.$element.prop('checked', value);
};
}
}
});

View File

@ -1,5 +1,8 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
/*
* form: controls a form and its registered inputs
@ -11,40 +14,83 @@ OHIF.mixins.form = new OHIF.Mixin({
const instance = Template.instance();
const component = instance.component;
// Define the form's data schema
const schema = instance.data.schema;
component.schema = schema && schema.newContext();
// Set the form identifier flag
component.isForm = true;
// Check if the form data is valid in its schema
component.validationObserver = new Tracker.Dependency();
// Reset the pathKey
instance.data.pathKey = '';
// Debound the observer call to prevent tons of re-rendering
component.validationRan = _.debounce(() => {
// Enable reactivity by changing a Tracker.Dependency observer
component.validationObserver.changed();
}, 200);
// Change the validation function to focus the fields with error
const validateSelf = component.validate;
component.validate = () => {
// Assume validation result as true
let result = true;
// Call the original validation function
validateSelf();
// Return true if there's no data schema defined
if (!component.schema) {
return result;
// Focus the first error field if some validation failed
if (component.schema && component.schema._invalidKeys.length) {
instance.$('.state-error :input:first').focus();
}
// Iterate over each registered form item and validate it
component.registeredItems.forEach(child => {
const key = child.templateInstance.data.key;
// Change result to false if any form item is invalid
if (key && !child.validate()) {
result = false;
}
});
// Return the validation result
return result;
};
},
onRendered() {
const instance = Template.instance();
const component = instance.component;
// Set the component main and style elements
component.$style = component.$element = instance.$('form:first');
},
events: {
'click .validation-error-container a'(event, instance) {
// Get the target key
const targetKey = $(event.currentTarget).attr('data-target');
// Focus the first input inside the element with error state
instance.$(`.state-error[data-key="${targetKey}"] :input:first`).focus();
}
},
helpers: {
validationErrors() {
const instance = Template.instance();
const component = instance.component;
// Create a dependency on child components validation
component.validationObserver.depend();
// Stop here if no schema was defined for the form
if (!component.schema) {
return;
}
// Check if there were some validation errors
if (component.schema._invalidKeys.length) {
const result = [];
// Iterate over each validation error and add to result
component.schema._invalidKeys.forEach(item => {
const label = component.schema._schema[item.name].label;
let message = component.schema.keyErrorMessage(item.name);
message = message.replace(label, `<strong>${label}</strong>`);
result.push({
key: item.name,
message: Spacebars.SafeString(message)
});
});
// Return the resulting validation errors
return result;
}
}
}
}
});

View File

@ -1,6 +1,7 @@
import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { Tracker } from "meteor/tracker";
import { Tracker } from 'meteor/tracker';
import { Meteor } from 'meteor/meteor';
import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
@ -31,7 +32,7 @@ OHIF.mixins.formItem = new OHIF.Mixin({
component.value = value => {
const isGet = _.isUndefined(value);
if (isGet) {
return component.$element.val();
return component.parseData(component.$element.val());
}
component.$element.val(value).trigger('change');
@ -53,6 +54,19 @@ OHIF.mixins.formItem = new OHIF.Mixin({
component.$wrapper[method]();
};
// Check if the focus is inside this element
component.hasFocus = () => {
// Get the focused element
const focused = $(':focus')[0];
// Check if the focused element is inside the component
const contains = $.contains(component.$wrapper[0], focused);
const isEqual = component.$wrapper[0] === focused;
// Return true if he component has the focus
return contains || isEqual;
};
// Add or remove a state from the component
component.state = (state, flag) => {
component.$wrapper.toggleClass(`state-${state}`, !!flag);
@ -65,24 +79,77 @@ OHIF.mixins.formItem = new OHIF.Mixin({
// Set or remove the error message
if (errorMessage) {
component.$element.trigger('errorin');
component.$wrapper.attr('data-error', errorMessage);
} else {
component.$element.trigger('errorout');
component.$wrapper.removeAttr('data-error', errorMessage);
}
};
// Toggle the tooltip over the component
component.toggleTooltip = (isShow, message) => {
if (isShow && message) {
// Stop here if the tooltip is already created
if (component.$wrapper.next('.tooltip').length) {
return;
}
// Create the tooltip
component.$wrapper.tooltip({
trigger: 'manual',
title: message
}).tooltip('show');
} else {
// Destroy the tooltip
component.$wrapper.tooltip('destroy');
}
};
// Toggle a state message as a tooltip over the component
component.toggleMessage = isShow => {
// Check if the action is to hide
if (!isShow) {
Meteor.setTimeout(() => {
// Check if the component has the focus
if (component.hasFocus()) {
// Prevent the tooltip from being hidden
return;
}
// Hide the tooltip
component.toggleTooltip(false);
}, 100);
return;
}
// Check for error state and message
const errorMessage = component.$wrapper.attr('data-error');
if (errorMessage) {
// Show the tooltip with the error message
component.toggleTooltip(true, errorMessage);
}
},
// Search for the parent form component
component.getForm = () => {
let currentComponent = component;
while (currentComponent) {
currentComponent = currentComponent.parent;
if (currentComponent && currentComponent.isForm) {
return currentComponent;
}
}
};
// Check if the component value is valid in its form's schema
component.validate = () => {
// Get the component's form
const form = component.parent;
const form = component.getForm();
// Get the form's data schema
const schema = form && form.schema;
// Get the current component's key
const key = instance.data.key;
const key = instance.data.pathKey;
// Return true if validation is not needed
if (!key || !schema || !component.$wrapper.is(':visible')) {
@ -90,12 +157,18 @@ OHIF.mixins.formItem = new OHIF.Mixin({
}
// Create the data document for validation
const document = {
const document = OHIF.blaze.getNestedObject({
[key]: component.value()
};
});
// Get the validation result
const validationResult = schema.validateOne(document, key);
// Notify the form that the validation ran
form.validationRan();
// Check if the document validation failed
if (!schema.validateOne(document, key)) {
if (!validationResult) {
// Set the component in error state and display the message
component.error(schema.keyErrorMessage(key));
@ -125,6 +198,9 @@ OHIF.mixins.formItem = new OHIF.Mixin({
// Set the most outer wrapper element
component.$wrapper = instance.wrapper.$('*').first();
// Add the pathKey to the wrapper element
component.$wrapper.attr('data-key', instance.data.pathKey);
},
onDestroyed() {
@ -143,24 +219,55 @@ OHIF.mixins.formItem = new OHIF.Mixin({
component.$style = component.$element;
}
// Set the component in jQuery data after all mixins are rendered
// Set the component in element and wrapper jQuery data
component.$element.data('component', component);
component.$wrapper.data('component', component);
},
events: {
// Enable reactivity by changing a Tracker.Dependency observer
// Handle the change event for the component
change(event, instance) {
instance.component.changeObserver.changed();
const component = instance.component;
// Prevent execution on upper components
if (event.currentTarget === component.$element[0]) {
// Enable reactivity by changing a Tracker.Dependency observer
component.changeObserver.changed();
// Revalidate the component
component.validate();
}
},
// TODO: [design] remove log, show error box/hint over the wrapper
errorin(event, instance) {
console.log('ERROR when validating component', instance.component);
focus(event, instance) {
const component = instance.component;
// Stop here if it is an group
if (component.isGroup || component.isCustomFocus) {
return;
}
// Prevent event bubbling
event.stopPropagation();
// Check for state messages and show it
component.toggleMessage(true);
},
// TODO: [design] hide error box/hint
errorout(event, instance) {
blur(event, instance) {
const component = instance.component;
// Stop here if it is an group
if (component.isGroup || component.isCustomFocus) {
return;
}
// Prevent event bubbling
event.stopPropagation();
// Hide state messages
component.toggleMessage(false);
}
}

View File

@ -12,29 +12,124 @@ OHIF.mixins.group = new OHIF.Mixin({
const instance = Template.instance();
const component = instance.component;
// Set the group identifier flag
component.isGroup = true;
// Run this computation every time the schema property is changed
instance.autorun(() => {
let schema = instance.data.schema;
// Check if the schema is reactive
if (schema instanceof ReactiveVar) {
// Register a dependency on schema property
schema = schema.get();
}
// Set the form's data schema
component.schema = schema && schema.newContext();
});
// Get or set the child components values
component.value = value => {
const isGet = _.isUndefined(value);
const isArray = instance.data.arrayValues;
// Create the result data as array or object
const result = isArray ? [] : {};
// Get the group current value and return it
if (isGet) {
const result = {};
// Iterate over each registered item and extract its value
component.registeredItems.forEach(child => {
const key = child.templateInstance.data.key;
if (key) {
result[key] = child.value();
// Check if it is an array or an object group
if (isArray) {
// Push the item value to the result array
result.push(child.value());
} else {
// Get the item key
const key = child.templateInstance.data.key;
//Check if a key is set for the item
if (key) {
// Add the item value to the result object
result[key] = child.value();
}
}
});
// Return the resulting data as array or object
return result;
}
const groupValue = typeof value === 'object' ? value : {};
// Get the group current value
const groupValue = typeof value === 'object' ? value : result;
// Stop here if there is no value defined for this group
if (!groupValue) {
return;
}
// Iterate over each registered item and set its value
let i = 0;
component.registeredItems.forEach(child => {
const key = child.templateInstance.data.key;
const key = isArray ? i : child.templateInstance.data.key;
const childValue = _.isUndefined(groupValue[key]) ? null : groupValue[key];
child.value(childValue);
i++;
});
// Trigger the change event after setting the new value
component.$element.trigger('change');
};
// Get a registered item in form by its key
component.item = itemKey => {
let found;
// Iterate over each registered form item
component.registeredItems.forEach(child => {
const key = child.templateInstance.data.key;
// Change the found item if current key is the same as given
if (key === itemKey) {
found = child;
}
});
// Return the found item or undefined if it was not found
return found;
};
// Check if the form data is valid in its schema
const validateSelf = component.validate;
component.validate = () => {
// Assume validation result as true
let result = true;
// Return true if there's no data schema defined
if (component.isForm && !component.schema) {
return result;
}
// Validate the component itself if it has a key
if (instance.data.pathKey && !validateSelf()) {
result = false;
}
// Iterate over each registered form item and validate it
component.registeredItems.forEach(child => {
const key = child.templateInstance.data.key;
// Change result to false if any form item is invalid
if ((key || instance.data.arrayValues) && !child.validate()) {
result = false;
}
});
// Return the validation result
return result;
};
// Disable or enable the component
component.disable = isDisable => {
component.registeredItems.forEach(child => child.disable(isDisable));

View File

@ -21,7 +21,7 @@ OHIF.mixins.groupRadio = new OHIF.Mixin({
component.registeredItems.forEach(child => elements.push(child.$element[0]));
const $elements = $(elements);
if (isGet) {
return $elements.filter(':checked').val();
return component.parseData($elements.filter(':checked').val());
}
$elements.filter(`[value='${value}']`).prop('checked', true).trigger('change');

View File

@ -7,7 +7,13 @@ import { _ } from 'meteor/underscore';
// Helper function to get the component's current schema
const getCurrentSchema = (parentComponent, key) => {
// Get the parent component schema
const schema = parentComponent && parentComponent.schema;
let schema = parentComponent && parentComponent.schema;
// Try to get the form schema if it was not found
if (parentComponent && !schema) {
const form = parentComponent.getForm();
schema = form && form.schema;
}
// Stop here if there's no key or schema defined
if (!key || !schema) {
@ -17,6 +23,11 @@ const getCurrentSchema = (parentComponent, key) => {
// Get the current schema data using component's key
const currentSchema = _.clone(schema._schema[key]);
// Stop here if no schema was found for the given key
if (!currentSchema) {
return;
}
// Merge the sub-schema properties if it's an array
if (Array.isArray(currentSchema.type())) {
_.extend(currentSchema, schema._schema[key + '.$']);
@ -40,8 +51,21 @@ OHIF.mixins.schemaData = new OHIF.Mixin({
// Get the parent component
const parent = OHIF.blaze.getParentComponent(Blaze.currentView);
// Get he parent component key
const parentKey = parent && parent.templateInstance.data.pathKey;
// Check if the parent is an array group
const isParentArray = parent && parent.templateInstance.data.arrayValues;
// Set the path key for this component
data.pathKey = data.key || (isParentArray ? '$' : '');
if (data.pathKey && typeof parentKey === 'string') {
const prefix = parentKey ? `${parentKey}.` : '';
data.pathKey = `${prefix}${data.pathKey}`;
}
// Get the current schema data using component's key
const currentSchema = getCurrentSchema(parent, data.key);
const currentSchema = getCurrentSchema(parent, data.pathKey);
// Stop here if there's no schema data for current key
if (!currentSchema) {
@ -53,6 +77,11 @@ OHIF.mixins.schemaData = new OHIF.Mixin({
data.label = new ReactiveVar(currentSchema.label);
}
// Set the emptyOption data attribute if given on schema
if (currentSchema.emptyOption) {
data.emptyOption = currentSchema.emptyOption;
}
// Fill the items if it's an array schema
if (!data.items && Array.isArray(currentSchema.allowedValues)) {
// Initialize the items array
@ -76,12 +105,41 @@ OHIF.mixins.schemaData = new OHIF.Mixin({
}
},
onCreated() {
const instance = Template.instance();
const component = instance.component;
// Create a data parser according to current schema key
component.parseData = value => {
// Get the current schema data using component's key
const currentSchema = getCurrentSchema(component.parent, instance.data.pathKey);
// Stop here if there's no schema data for current key
if (!currentSchema) {
return;
}
// Check if the schema is a Number
if (currentSchema.type === Number) {
return parseFloat(value);
}
// Check if the schema is a Boolean
if (currentSchema.type === Boolean) {
return !!value;
}
// Return the original value if none of the checks matched
return value;
};
},
onMixins() {
const instance = Template.instance();
const component = instance.component;
// Get the current schema data using component's key
const currentSchema = getCurrentSchema(component.parent, instance.data.key);
const currentSchema = getCurrentSchema(component.parent, instance.data.pathKey);
// Stop here if there's no schema data for current key
if (!currentSchema) {

View File

@ -8,6 +8,46 @@ import { $ } from 'meteor/jquery';
OHIF.mixins.select2 = new OHIF.Mixin({
dependencies: 'select',
composition: {
onCreated() {
const instance = Template.instance();
// Set the custom focus flag
instance.component.isCustomFocus = true;
// Check if this select will include a placeholder
const placeholder = instance.data.options && instance.data.options.placeholder;
if (placeholder) {
// Get the option items
let items = instance.data.items;
// Check if the items are reactive and get them if true
const isReactive = items instanceof ReactiveVar;
if (isReactive) {
items = items.get();
}
// Check if there is already an empty option on items list
const query = {
value: ''
};
if (!_.findWhere(items, query)) {
// Clone the current items
const newItems = _.clone(items) || [];
newItems.unshift({
label: placeholder,
value: ''
});
// Set the new items list including the empty option
if (isReactive) {
instance.data.items.set(newItems);
} else {
instance.data.items = newItems;
}
}
}
},
onRendered() {
const instance = Template.instance();
const component = instance.component;
@ -17,6 +57,22 @@ OHIF.mixins.select2 = new OHIF.Mixin({
// Store the select2 instance to allow its further destruction
component.select2Instance = component.$element.data('select2');
// Get the focusable elements
const elements = [];
elements.push(component.$element[0]);
elements.push(component.$element.nextAll('.select2:first').find('.select2-selection')[0]);
// Attach focus and blur handlers to focusable elements
$(elements).on('focus', event => {
event.stopPropagation();
// Show the state message on elements focus
component.toggleMessage(true);
}).on('blur', event => {
event.stopPropagation();
// Hide the state message on elements blur
component.toggleMessage(false);
});
},
onDestroyed() {
@ -25,14 +81,6 @@ OHIF.mixins.select2 = new OHIF.Mixin({
// Destroy the select2 instance to remove unwanted DOM elements
component.select2Instance.destroy();
},
events: {
'focus .select2-hidden-accessible'(event, instance) {
// Redirect the focus to select2 focus control in case of hidden
// accessible being focused (e.g. clicking on outer label)
$(event.currentTarget).nextAll('.select2:first').find('.select2-selection').focus();
}
}
}
});

View File

@ -30,7 +30,7 @@ OHIF.mixins.selectTree = new OHIF.Mixin({
// Return the current value
if (isGet) {
return rootInstance.currentValue;
return component.parseData(rootInstance.currentValue);
}
// Change the current value

View File

@ -11,6 +11,13 @@
target="{{this.target}}"
{{this.tagAttributes}}
>
{{#if validationErrors}}
<div class="validation-error-container alert alert-danger" role="alert">
{{#each error in validationErrors}}
<p><a href="#" class="text-danger" data-target="{{error.key}}">{{error.message}}</a></p>
{{/each}}
</div>
{{/if}}
{{>UI.contentBlock}}
</form>
</template>

View File

@ -4,6 +4,7 @@
class="{{this.class}}"
name="{{this.name}}"
multiple="{{#if this.multiple}}multiple{{/if}}"
disabled="{{#if this.disabled}}disabled{{/if}}"
{{this.tagAttributes}}
>
{{>UI.contentBlock}}

View File

@ -1,6 +1,8 @@
import './form/form.html';
import './form/group.html';
import './input/checkbox.html';
import './input/hidden.html';
import './input/groupRadio.html';
import './input/radio.html';
import './input/select.html';

View File

@ -0,0 +1,15 @@
<template name="inputCheckbox">
{{#baseComponent (extend this
base='baseInput'
type='checkbox'
mixins=(concat 'checkbox ' this.mixins)
labelAfter=(valueIf (isUndefined this.labelAfter) true this.labelAfter)
labelClass=(concat 'checkboxLabel ' this.labelClass)
wrappers=(concat ''
(valueIf reactive this.label 'wrapperLabel ' '')
this.wrappers
)
)}}
{{>UI.contentBlock}}
{{/baseComponent}}
</template>

View File

@ -0,0 +1,7 @@
<template name="inputHidden">
{{>baseComponent (extend this
base='baseInput'
type='hidden'
mixins=(concat 'input ' this.mixins)
)}}
</template>

View File

@ -4,7 +4,7 @@
type='radio'
mixins=(concat 'input ' this.mixins)
labelAfter=(valueIf (isUndefined this.labelAfter) true this.labelAfter)
labelClass=(concat 'checkboxLabel ' this.labelClass)
labelClass=(concat 'radioLabel ' this.labelClass)
wrappers=(concat ''
(valueIf reactive this.label 'wrapperLabel ' '')
this.wrappers

View File

@ -11,17 +11,19 @@
<div class="tree-content">
{{#let currentNode=this.currentNode.get}}
{{#if (and (not this.root) (ne this.search false))}}
{{>inputText
{{#inputText
label=this.label
labelClass='tree-search'
placeholder=(choose this.searchPlaceholder 'Search')
}}
<i class="fa fa-search"></i>
{{/inputText}}
{{/if}}
<div class="tree-options {{#if currentNode}}collapsed{{/if}}">
{{#if this.root}}
{{>selectTreeBreadcrumb component=this.component}}
{{/if}}
<div class="tree-inputs">
<div class="tree-inputs clearfix">
{{#each item in treeItems}}
{{>inputRadio
id=(concat this.storageKey '_' (encodeId item.value))
@ -45,8 +47,8 @@
</div>
{{/let}}
</div>
{{#if (and (not this.root) this.storageKey)}}
{{>selectTreeCommon (clone this component=this.component)}}
{{#if (and (not this.root) (or this.storageKey this.commonItems))}}
{{>selectTreeCommon (clone this component=this.component treeInstance=instance)}}
{{/if}}
{{/group}}
</template>

View File

@ -11,6 +11,45 @@ import transition from 'meteor/ohif:core/client/lib/third-party/transition-to-fr
Template.selectTree.onCreated(() => {
const instance = Template.instance();
// Return the common items
instance.getCommonItems = () => {
const instance = Template.instance();
// Return the common items if given
if (instance.data.commonItems) {
return instance.data.commonItems;
}
// Get all the tree leaves
const leaves = instance.data.component.getLeaves();
// Generate an object with encoded keys from the tree leaves
leavesObject = {};
_.each(leaves, leaf => {
leavesObject[OHIF.string.encodeId(leaf.value)] = leaf;
});
// Get the current items ranking
const ranking = OHIF.user.getData(instance.data.storageKey);
// Sort the items based on how many times each one was used
const sorted = [];
_.each(ranking, (count, key) => sorted.push([key, count]));
sorted.sort((a, b) => b[1] - a[1]);
// Create the result and push every item respecting the ranking order
const result = [];
_.each(sorted, item => {
const current = leavesObject[item[0]];
if (current) {
result.push(current);
}
});
// Return the resulting array
return result;
};
// Create a reactive variable to control the search
instance.searchTerm = new ReactiveVar('');
});
@ -25,7 +64,10 @@ Template.selectTree.onRendered(() => {
instance.component = component;
// Set the margin to display the common section
$treeRoot.children('.tree-content').css('margin-right', $treeRoot.width());
const isTreeColumns = instance.data.treeColumns;
const marginProperty = isTreeColumns ? 'margin-left' : 'margin-right';
const marginWidth = isTreeColumns ? $treeRoot.width() / 2 : $treeRoot.width();
$treeRoot.children('.tree-content').css(marginProperty, marginWidth);
// Make the component respect the window boundaries
$treeRoot.bounded();
@ -144,6 +186,18 @@ Template.selectTree.events({
$treeRoot.addClass('interacted');
},
'keydown .tree-search input'(event, instance) {
// Get the search term
const searchTerm = $(event.currentTarget).val().trim();
const $labels = instance.$('.tree-inputs:first>label');
// Select the label if ENTER was pressed and there is only one result
if (searchTerm.length && $labels.length === 1 && event.which === 13) {
$labels.find('input').click();
}
},
'change .select-tree:first>.tree-content>.tree-options>.tree-inputs>label>input'(event, instance) {
const component = instance.component;
const $target = $(event.currentTarget);
@ -235,27 +289,6 @@ Template.selectTree.events({
currentInstance = currentInstance.component.parent.templateInstance;
}
}
},
'click .select-tree-common label'(event, instance) {
// Get the clicked label
const $target = $(event.currentTarget);
// Build the input selector based on the label target
const inputSelector = '#' + $target.attr('for');
// Check if the input is not rendered
if (!$(inputSelector).length) {
// Change the search terms with the clicked label string
instance.$('.tree-search input').val($target.text()).trigger('input');
// Wait for options rerendering
Tracker.afterFlush(() => {
// Wait for components initialization
Meteor.defer(() => $(inputSelector).click());
});
}
}
});
@ -276,6 +309,23 @@ Template.selectTree.helpers({
// Filter only the tree leaves
items = _.filter(items, item => !item.items);
} else if (instance.data.hideCommon) {
// Get the values of common items
const commonValues = _.pluck(instance.getCommonItems(), 'value');
// Filter only the items that are not common
items = _.filter(items, item => !_.contains(commonValues, item.value));
}
// Return the items sorted for tree columns
if (instance.data.treeColumns) {
const begin = items.splice(0, Math.ceil(items.length / 2));
const sortedItems = [];
_.each(begin, (item, index) => {
sortedItems.push(item);
items[index] && sortedItems.push(items[index]);
});
items = sortedItems;
}
// Return the tree items

View File

@ -2,38 +2,35 @@ import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
Template.selectTreeCommon.helpers({
// Get the common items ordered by use history
items() {
const instance = Template.instance();
Template.selectTreeCommon.events({
'click .select-tree-common label'(event, instance) {
// Get the clicked label
const $target = $(event.currentTarget);
// Get all the tree leaves
const leaves = instance.data.component.getLeaves();
// Build the input selector based on the label target
const inputSelector = '#' + $target.attr('for');
// Generate an object with encoded keys from the tree leaves
leavesObject = {};
_.each(leaves, leaf => {
leavesObject[OHIF.string.encodeId(leaf.value)] = leaf;
});
// Get the tree instance
const treeInstance = instance.data.treeInstance;
// Get the current items ranking
const ranking = OHIF.user.getData(instance.data.storageKey);
// Check if the input is not rendered
if (!$(inputSelector).length) {
// Change the search terms with the clicked label string
// Sort the items based on how many times each one was used
const sorted = [];
_.each(ranking, (count, key) => sorted.push([key, count]));
sorted.sort((a, b) => b[1] - a[1]);
treeInstance.$('.tree-search input').val($target.text()).trigger('input');
// Wait for options rerendering
// Create the result and push every item respecting the ranking order
const result = [];
_.each(sorted, item => {
const current = leavesObject[item[0]];
if (current) {
result.push(current);
}
});
// Return the resulting array
return result;
Tracker.afterFlush(() => {
// Wait for components initialization
Meteor.defer(() => $(inputSelector).click());
});
}
}
});
Template.selectTreeCommon.helpers({
// Get the common items
items() {
return Template.instance().data.treeInstance.getCommonItems();
}
});

View File

@ -1,6 +1,6 @@
<template name="componentPlayground">
<div class="component-playground">
{{>measureFlow}}
{{>measureFlow treeColumns=true hideCommon=true}}
<!--
<div class="p-a-3">
{{>selectTree

View File

@ -31,7 +31,7 @@ OHIF.blaze.getParentView = (view, parentViewName) => {
};
// Search for the parent component of the given view
OHIF.blaze.getParentComponent = (view) => {
OHIF.blaze.getParentComponent = view => {
let currentView = view;
while (currentView) {
currentView = currentView.originalParentView || currentView.parentView;

View File

@ -1,4 +1,5 @@
import './blaze.js';
import './object.js';
import './string.js';
import './ui.js';
import './user.js';

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