WIP reorganization
This commit is contained in:
1 parent
6706dfe634
commit
054e37dfb7
266 files changed
+98
-620
No files matched your search
@@ -0,0 +1,10 @@
|
||||
import './item.html';
|
||||
import './item.js';
|
||||
import './list.html';
|
||||
import './list.styl';
|
||||
import './quickSwitch.html';
|
||||
import './quickSwitch.js';
|
||||
import './sidebar.html';
|
||||
import './sidebar.js';
|
||||
import './studies.html';
|
||||
import './studies.js';
|
||||
@@ -0,0 +1,21 @@
|
||||
<template name="timepointBrowserItem">
|
||||
<div class="timepoint-browser-item" data-id="{{this.timepoint.timepointId}}">
|
||||
<div class="timepoint-item p-y-2">
|
||||
<div class="timepoint-details clearfix">
|
||||
<div class="timepoint-title pull-left">
|
||||
{{this.timepointApi.title timepoint}}
|
||||
</div>
|
||||
<div class="timepoint-expand-icon pull-right">
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</div>
|
||||
<div class="timepoint-date pull-right m-r-1">{{formatDA this.timepoint.earliestDate 'D-MMM-YYYY'}}</div>
|
||||
</div>
|
||||
<div class="timepoint-summary">{{choose instance.summary.get 'Click to load'}}</div>
|
||||
</div>
|
||||
{{#if this.timepointChildTemplate}}
|
||||
{{#if instance.studiesData.get}}
|
||||
{{>Template.dynamic template=this.timepointChildTemplate data=(clone this studiesInformation=instance.studiesData.get)}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { $ } from 'meteor/jquery';
|
||||
import { _ } from 'meteor/underscore';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
Template.timepointBrowserItem.onCreated(() => {
|
||||
const instance = Template.instance();
|
||||
const { timepoint, timepointApi } = instance.data;
|
||||
const { timepointId } = timepoint;
|
||||
|
||||
const hasStudiesData = !!(timepoint.studiesData && timepoint.studiesData.length);
|
||||
instance.summary = new ReactiveVar('');
|
||||
instance.studiesData = new ReactiveVar(hasStudiesData ? timepoint.studiesData : null);
|
||||
|
||||
const updateStudiesData = newDocument => {
|
||||
const newTimepoint = newDocument || timepointApi.timepoints.findOne({ timepointId });
|
||||
if (newTimepoint && newTimepoint.studiesData && newTimepoint.studiesData.length) {
|
||||
instance.studiesData.set(newTimepoint.studiesData);
|
||||
}
|
||||
};
|
||||
|
||||
timepointApi.timepoints.find({ timepointId }).observe({ changed: updateStudiesData });
|
||||
|
||||
// Build the modalities summary of all timepoint's studies
|
||||
instance.setModalitiesSummary = () => {
|
||||
const studiesData = instance.studiesData.get();
|
||||
if (!studiesData) return;
|
||||
|
||||
const modalities = {};
|
||||
studiesData.forEach(study => {
|
||||
const modality = study.modalities || 'UN';
|
||||
modalities[modality] = modalities[modality] + 1 || 1;
|
||||
});
|
||||
|
||||
const summary = [];
|
||||
_.each(modalities, (count, modality) => summary.push(`${count} ${modality}`));
|
||||
|
||||
instance.summary.set(summary.join(', '));
|
||||
};
|
||||
|
||||
const filter = { studyInstanceUid: timepoint.studyInstanceUids };
|
||||
instance.loadStudies = () => OHIF.studies.searchStudies(filter).then(studiesData => {
|
||||
instance.studiesData.set(studiesData);
|
||||
timepointApi.timepoints.update(timepoint._id, { $set: { studiesData } });
|
||||
instance.setModalitiesSummary();
|
||||
}).catch(error => {
|
||||
const text = 'An error has occurred while retrieving studies information';
|
||||
OHIF.ui.notifications.danger({ text });
|
||||
OHIF.log.error(error);
|
||||
instance.summary.set('Failed');
|
||||
});
|
||||
|
||||
updateStudiesData();
|
||||
instance.autorun(() => {
|
||||
const studiesData = instance.studiesData.get();
|
||||
if (studiesData) {
|
||||
instance.setModalitiesSummary();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Template.timepointBrowserItem.events({
|
||||
'ohif.measurements.timepoint.load .timepoint-item'(event, instance) {
|
||||
instance.loadStudies();
|
||||
},
|
||||
|
||||
'click .timepoint-item'(event, instance) {
|
||||
const element = event.currentTarget.parentElement;
|
||||
const $element = $(element);
|
||||
|
||||
const triggerClick = () => {
|
||||
$element.trigger('ohif.measurements.timepoint.click', instance.data.timepoint);
|
||||
};
|
||||
|
||||
if (!instance.studiesData.get()) {
|
||||
instance.summary.set('Loading...');
|
||||
instance.loadStudies().then(() => Tracker.afterFlush(triggerClick));
|
||||
} else {
|
||||
triggerClick();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
<template name="timepointBrowserList">
|
||||
<div class="timepoint-browser-list {{this.class}}">
|
||||
{{#each timepoint in this.timepoints}}
|
||||
{{>timepointBrowserItem (clone this timepoint=timepoint)}}
|
||||
<hr>
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,85 @@
|
||||
@require '{ohif:design}/app'
|
||||
|
||||
.timepoint-switch
|
||||
theme('border-bottom', '1px solid $uiBorderColor')
|
||||
|
||||
.timepoint-browser-item
|
||||
|
||||
.timepoint-item
|
||||
cursor: pointer
|
||||
font-size: 13px
|
||||
|
||||
&:not(.active) .timepoint-browser-studies
|
||||
max-height: 0 !important
|
||||
|
||||
.timepoint-studies-container
|
||||
opacity: 0
|
||||
transform(translateY(-100%))
|
||||
|
||||
.timepoint-browser-studies
|
||||
overflow: hidden
|
||||
transition(max-height 0.3s ease)
|
||||
|
||||
.timepoint-studies-container
|
||||
opacity: 1
|
||||
transition(opacity 0.3s ease\, transform 0.3s ease)
|
||||
transform(translateY(0))
|
||||
transform-origin(50% 0%)
|
||||
|
||||
.timepoint-date
|
||||
opacity: 1
|
||||
|
||||
&.active
|
||||
.timepoint-summary
|
||||
max-height: 0
|
||||
opacity: 0
|
||||
|
||||
.timepoint-date
|
||||
opacity: 0
|
||||
|
||||
.timepoint-expand-icon i
|
||||
transform: rotateX(180deg)
|
||||
|
||||
.timepoint-title
|
||||
font-size: 14px
|
||||
theme('color', '$textSecondaryColor')
|
||||
text-transform: uppercase
|
||||
|
||||
&
|
||||
.timepoint-expand-icon i,
|
||||
.timepoint-title,
|
||||
.timepoint-date,
|
||||
.timepoint-summary
|
||||
theme('color', '$textSecondaryColor')
|
||||
transition($sidebarTransition)
|
||||
|
||||
&:hover
|
||||
&,
|
||||
.timepoint-title,
|
||||
.timepoint-expand-icon i,
|
||||
.timepoint-date,
|
||||
.timepoint-summary
|
||||
theme('color', '$textPrimaryColor') !important
|
||||
|
||||
.timepoint-details
|
||||
.timepoint-summary
|
||||
line-height: 25px
|
||||
|
||||
.timepoint-summary
|
||||
height: 25px
|
||||
max-height: 25px
|
||||
opacity: 1
|
||||
overflow: hidden
|
||||
|
||||
.timepoint-expand-icon i
|
||||
theme('color', '$defaultColor')
|
||||
|
||||
.timepoint-title
|
||||
theme('color', '$textPrimaryColor')
|
||||
padding-top: 2px
|
||||
|
||||
.series-quick-switch .timepoint-browser-list
|
||||
padding: 0 10px
|
||||
|
||||
div.timepoint-title
|
||||
text-transform: none
|
||||
@@ -0,0 +1,3 @@
|
||||
<template name="timepointBrowserQuickSwitch">
|
||||
{{>timepointBrowserList (extend timepointBrowserData)}}
|
||||
</template>
|
||||
@@ -0,0 +1,119 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import { Tracker } from 'meteor/tracker';
|
||||
import { _ } from 'meteor/underscore';
|
||||
import { OHIF } from 'meteor/ohif:core';
|
||||
|
||||
Template.timepointBrowserQuickSwitch.onCreated(() => {
|
||||
const instance = Template.instance();
|
||||
const { timepointApi } = OHIF.viewer;
|
||||
|
||||
instance.selectedTimepoint = new ReactiveVar();
|
||||
instance.timepoints = new ReactiveVar([]);
|
||||
|
||||
instance.updateSelectedTimepoint = studyInstanceUid => {
|
||||
const selectedTimepoint = timepointApi.study(studyInstanceUid)[0];
|
||||
instance.selectedTimepoint.set(selectedTimepoint);
|
||||
};
|
||||
|
||||
const currentTimepoint = timepointApi.current();
|
||||
const filter = { latestDate: { $lte: currentTimepoint.latestDate } };
|
||||
instance.keyTimepoints = timepointApi.key(filter);
|
||||
|
||||
const { viewportIndex } = instance.data;
|
||||
instance.autorun(() => {
|
||||
OHIF.viewerbase.layoutManager.observer.depend();
|
||||
const viewportData = OHIF.viewerbase.layoutManager.viewportData[viewportIndex];
|
||||
let { studyInstanceUid } = viewportData;
|
||||
if (!studyInstanceUid) {
|
||||
Tracker.nonreactive(() => {
|
||||
const currentStudy = instance.data.currentStudy.get();
|
||||
if (currentStudy) {
|
||||
studyInstanceUid = currentStudy.studyInstanceUid;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
instance.updateSelectedTimepoint(studyInstanceUid);
|
||||
});
|
||||
|
||||
instance.autorun(() => {
|
||||
const selectedTimepoint = instance.selectedTimepoint.get();
|
||||
const timepoints = [selectedTimepoint];
|
||||
instance.timepoints.set(timepoints);
|
||||
});
|
||||
});
|
||||
|
||||
Template.timepointBrowserQuickSwitch.onRendered(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
instance.updateActiveStudy = () => {
|
||||
const currentStudy = instance.data.currentStudy.get();
|
||||
const studyInstanceUid = (currentStudy && currentStudy.studyInstanceUid) || '';
|
||||
Tracker.afterFlush(() => {
|
||||
const $studyBrowserItems = instance.$('.study-browser-item');
|
||||
$studyBrowserItems.removeClass('active');
|
||||
$studyBrowserItems.filter(`[data-uid="${studyInstanceUid}"]`).addClass('active');
|
||||
});
|
||||
};
|
||||
|
||||
instance.autorun(() => {
|
||||
const selectedTimepoint = instance.selectedTimepoint.get();
|
||||
const selectedTimepointId = (selectedTimepoint && selectedTimepoint.timepointId) || '';
|
||||
const $allBrowserItems = instance.$('.timepoint-browser-item');
|
||||
const $browserItem = $allBrowserItems.filter(`[data-id=${selectedTimepointId}]`);
|
||||
if (!$browserItem.hasClass('active')) {
|
||||
$browserItem.find('.timepoint-item').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
instance.autorun(instance.updateActiveStudy);
|
||||
});
|
||||
|
||||
Template.timepointBrowserQuickSwitch.events({
|
||||
'ohif.measurements.timepoint.click'(event, instance) {
|
||||
const $element = $(event.currentTarget);
|
||||
$element.toggleClass('active');
|
||||
instance.updateActiveStudy();
|
||||
},
|
||||
|
||||
'ohif.studies.study.click'(event, instance, studyInformation) {
|
||||
const { studyInstanceUid } = studyInformation;
|
||||
const study = OHIF.viewer.Studies.findBy({ studyInstanceUid });
|
||||
instance.data.currentStudy.set(study);
|
||||
const $studySwitch = $(event.currentTarget).closest('.study-switch');
|
||||
$studySwitch.siblings('.series-switch').trigger('rescale');
|
||||
instance.updateSelectedTimepoint(studyInformation.studyInstanceUid);
|
||||
|
||||
// Create a hover bridge to prevent quick switch from closing due to scroll height
|
||||
Meteor.defer(() => {
|
||||
const $scrollable = $studySwitch.find('.study-browser>.scrollable');
|
||||
const offsetY = $scrollable.offset().top + $scrollable.outerHeight();
|
||||
if (event.clientY > offsetY) {
|
||||
const hoverHandler = _.throttle(event => {
|
||||
if (event.clientY <= offsetY) {
|
||||
$scrollable.css('padding-bottom', '');
|
||||
$scrollable.off('mousemove', hoverHandler);
|
||||
$scrollable.off('mouseleave', hoverHandler);
|
||||
}
|
||||
}, 100);
|
||||
$scrollable.css('padding-bottom', event.clientY - offsetY + 20);
|
||||
$scrollable.on('mousemove', hoverHandler);
|
||||
$scrollable.one('mouseleave', () => $scrollable.off('mousemove', hoverHandler));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Template.timepointBrowserQuickSwitch.helpers({
|
||||
timepointBrowserData() {
|
||||
const instance = Template.instance();
|
||||
const { timepointApi } = OHIF.viewer;
|
||||
return {
|
||||
timepointApi,
|
||||
timepoints: instance.timepoints.get(),
|
||||
timepointChildTemplate: 'timepointBrowserStudies'
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<template name="timepointBrowserSidebar">
|
||||
<div class="full-height flex-v noselect">
|
||||
<div class="p-x-1">
|
||||
<div class="timepoint-switch p-y-2">
|
||||
{{>roundedButtonGroup viewTypeButtonGroupData}}
|
||||
</div>
|
||||
</div>
|
||||
{{#scrollArea class='flex-grow fit' scrollableClass='p-a-1' scrollStep=191}}
|
||||
{{>timepointBrowserList (extend timepointBrowserData)}}
|
||||
{{/scrollArea}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Template } from 'meteor/templating';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
|
||||
Template.timepointBrowserSidebar.onCreated(() => {
|
||||
const instance = Template.instance();
|
||||
|
||||
// Reactive variable to control the view type for all or key timepoints
|
||||
instance.timepointViewType = new ReactiveVar(instance.data.timepointViewType);
|
||||
});
|
||||
|
||||
Template.timepointBrowserSidebar.onRendered(() => {
|
||||
const instance = Template.instance();
|
||||
instance.lastType = '';
|
||||
|
||||
// Collapse all timepoints but first when timepoint view type changes
|
||||
instance.$browserList = instance.$('.timepoint-browser-list').first();
|
||||
instance.autorun(() => {
|
||||
// Runs this computation every time the timepointViewType is changed
|
||||
const type = instance.timepointViewType.get();
|
||||
if (type !== instance.lastType) {
|
||||
const eventKey = 'ohif.measurements.timepoint.changeViewType';
|
||||
instance.$browserList.trigger(eventKey, type);
|
||||
}
|
||||
|
||||
instance.lastType = type;
|
||||
});
|
||||
});
|
||||
|
||||
Template.timepointBrowserSidebar.events({
|
||||
'ohif.studies.study.click'(event, instance) {
|
||||
const $element = $(event.currentTarget);
|
||||
|
||||
// Defer the active class toggling to wait for child template rendering
|
||||
Meteor.defer(() => {
|
||||
// Remove max height restriction from studies browser
|
||||
const $studiesBrowser = $element.closest('.timepoint-browser-studies');
|
||||
$studiesBrowser.css('max-height', '');
|
||||
|
||||
// Remove active class from sibling studies
|
||||
$element.siblings().removeClass('active');
|
||||
|
||||
// Toggle the active class on clicked study
|
||||
$element.toggleClass('active');
|
||||
|
||||
// Adjust the max height for studiesBrowser when series transition is finished
|
||||
const $seriesBrowser = $element.find('.study-browser-series');
|
||||
$seriesBrowser.one('transitionend', () => $studiesBrowser.adjustMax('height'));
|
||||
});
|
||||
},
|
||||
|
||||
'ohif.measurements.timepoint.click'(event, instance) {
|
||||
const $element = $(event.currentTarget);
|
||||
|
||||
// Defer the active class toggling to wait for child template rendering
|
||||
Meteor.defer(() => $element.toggleClass('active'));
|
||||
}
|
||||
});
|
||||
|
||||
Template.timepointBrowserSidebar.helpers({
|
||||
viewTypeButtonGroupData() {
|
||||
return {
|
||||
value: Template.instance().timepointViewType,
|
||||
options: [{
|
||||
value: 'key',
|
||||
text: 'Key Timepoints'
|
||||
}, {
|
||||
value: 'all',
|
||||
text: 'All Timepoints'
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
timepointBrowserData() {
|
||||
const instance = Template.instance();
|
||||
const { timepointApi } = instance.data;
|
||||
|
||||
const currentTimepoint = timepointApi.current();
|
||||
const { patientId } = currentTimepoint;
|
||||
let timepoints = [];
|
||||
if (instance.timepointViewType.get() === 'key') {
|
||||
const filter = { latestDate: { $lte: currentTimepoint.latestDate } };
|
||||
timepoints = timepointApi.key(filter);
|
||||
} else {
|
||||
timepoints = timepointApi.all({ patientId });
|
||||
}
|
||||
|
||||
return {
|
||||
timepointApi,
|
||||
timepoints,
|
||||
timepointChildTemplate: 'timepointBrowserStudies',
|
||||
studyChildTemplate: 'studyBrowserSeries'
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
<template name="timepointBrowserStudies">
|
||||
<div class="timepoint-browser-studies">
|
||||
<div class="timepoint-studies-container">
|
||||
{{>studyBrowserList this}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Template } from 'meteor/templating';
|
||||
|
||||
Template.timepointBrowserStudies.onRendered(() => {
|
||||
Template.instance().$('.timepoint-browser-studies').adjustMax('height');
|
||||
});
|
||||
Reference in new issue
Block a user