LT-275: Creating measure flow component animations

This commit is contained in:
Bruno Alves de Faria 2016-07-29 09:03:35 -03:00
parent 7fdfbf1c1a
commit 9a8eaff560
7 changed files with 404 additions and 73 deletions

View File

@ -8,13 +8,53 @@ $gray4 = #C0C0C0
$gray5 = #9B9B9B
$gray6 = #303030
.select-tree-root
.select-tree
background-color: white
&, a.tree-back, .tree-breadcrumb span, .tree-node, .tree-leaf
transition(all 0.3s ease)
.select-tree-root
&>*:first-child
transition(all 0.3s ease)
.select-tree-root.selected
border-color: transparent
border-radius: 0
// height: 26px
&, .select-tree
background-color: transparent
&:not(.open)>.tree-options
&>.tree-inputs, &>.tree-breadcrumb
display: none
&.open>.tree-options
&>.tree-inputs>label:not(.active), &>.tree-breadcrumb a, &>.tree-breadcrumb span
color: transparent
&>.tree-inputs>label.tree-node:not(.active)
border-color: transparent
.tree-search
opacity: 0
&>.tree-options
background-color: transparent
label.tree-leaf.active
box-shadow: 0 0 0 10px white, inset 0 0 0 200px white
.select-tree-root
border: 1px solid $gray3
border-radius: 5px
display: table
display: inline-block
overflow: hidden
&>.tree-options
transform(scale(1))
transition(height 0.3s ease)
.tree-search
background-color: $gray4
border-bottom: 1px solid $gray3
@ -36,12 +76,32 @@ $gray6 = #303030
line-height: 32px
padding: 5px 12px
.path-link, .tree-current-node
display: inline-block
font-weight: normal
float: left
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
i
margin-right: 5px
.select-tree-node
left: 0
position: fixed
width: 100%
z-index: 2
.tree-options
overflow: hidden
position: relative
z-index: 2
.tree-node, .tree-leaf
cursor: pointer
display: block
font-weight: normal
margin-bottom: 0
padding: 0 12px
position: relative
@ -61,12 +121,13 @@ $gray6 = #303030
border-bottom: 0
.tree-leaf
box-shadow: 0 0 0 200px transparent, inset 0 0 0 200px transparent
line-height: 26px
label
transition(all 0.3s ease)
&.active
box-shadow: 0 0 0 200px white, inset 0 0 0 200px white
transition(box-shadow 0.3s ease)
z-index: 0
&.collapsed
&>.tree-breadcrumb, &>label:not(.active)
display: none
z-index: 2

View File

@ -1,3 +1,10 @@
animation(a)
-webkit-animation: a
-moz-animation: a
-ms-animation: a
-o-animation: a
animation: a
transition(t)
transition: t
-webkit-transition: t

View File

@ -2,7 +2,6 @@ import { OHIF } from 'meteor/ohif:core';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
/*
* input: controls a tree selection component
@ -13,6 +12,8 @@ OHIF.mixins.selectTree = new OHIF.Mixin({
onCreated() {
const instance = Template.instance();
const component = instance.component;
const rootComponent = instance.data.root || component;
const rootInstance = rootComponent.templateInstance;
// Create the component's value storage property
instance.data.currentNode = new ReactiveVar(null);
@ -41,49 +42,6 @@ OHIF.mixins.selectTree = new OHIF.Mixin({
// Change the current node
instance.data.currentNode.set(node);
};
},
events: {
'change .select-tree:first>.tree-options>label>input'(event, instance) {
const component = instance.component;
const $target = $(event.target);
const eventComponent = $target.data('component');
// Change the active item
const rootComponent = instance.data.root || component;
rootComponent.templateInstance.$('label').removeClass('active');
$target.closest('label.tree-leaf').addClass('active');
// Change the component's value
component.value(eventComponent.value());
},
'click .select-tree:first>.tree-options>.tree-breadcrumb>.tree-back'(event, instance) {
// Prevent the default element action
event.preventDefault();
// Reset the checked inputs to unchecked state
const rootComponent = instance.data.root || instance.component;
rootComponent.templateInstance.$('input').removeAttr('checked');
// Get the index of the breadcrumb's clicked option
const index = $(event.currentTarget).attr('data-index') | 0;
// Set the current instance
let currentInstance = instance.component.parent.templateInstance;
// Iterate over all parents until gets into the clicked node
for (let i = 1; i <= index; i++) {
// Unset the selected node
currentInstance.data.currentNode.set(null);
// Check if it's not the root node
if (i < index) {
// Change the current instance
currentInstance = currentInstance.component.parent.templateInstance;
}
}
}
}
}
});

View File

@ -6,3 +6,4 @@ import './input/radio.html';
import './input/select.html';
import './input/text.html';
import './input/selectTree.html';
import './input/selectTree.js';

View File

@ -20,30 +20,32 @@
{{#if this.root}}
{{>selectTreeBreadcrumb component=this.component}}
{{/if}}
{{#each item in (reactive this.items)}}
{{>inputRadio
class=this.radioClass
name=this.key
value=item.value
label=item.label
labelClass=(valueIf (isArray item.items) 'tree-node' 'tree-leaf')
checked=(eq item.value (reactive this.value))
}}
{{/each}}
<div class="tree-inputs">
{{#each item in (reactive this.items)}}
{{>inputRadio
class=this.radioClass
name=this.key
value=item.value
label=item.label
labelClass=(valueIf (isArray item.items) 'tree-node' 'tree-leaf')
checked=(eq item.value (reactive this.value))
}}
{{/each}}
</div>
{{#if (and currentNode (isArray currentNode.items))}}
{{>selectTree (clone this
items=currentNode.items
root=(choose this.root this.component)
class='select-tree-node'
)}}
{{/if}}
</div>
{{#if (and currentNode (isArray currentNode.items))}}
{{>selectTree (clone this
items=currentNode.items
root=(choose this.root this.component)
class='select-tree-node'
)}}
{{/if}}
{{/let}}
{{/group}}
</template>
<template name="selectTreeBreadcrumb">
<div class="tree-breadcrumb">
<div class="tree-breadcrumb clearfix">
{{>selectTreeBreadcrumbParent
component=this.component.parent
index=1
@ -62,11 +64,17 @@
component=parent
index=(sum this.index 1)
}}
<a href="#" class="tree-back" data-index="{{this.index}}">{{parentNode.label}}</a>
<span>/</span>
{{else}}
<a href="#" class="tree-back" data-index="{{this.index}}"><i class="fa fa-fast-backward"></i></a>
{{/if}}
<span class="path-link">
<a href="#" class="tree-back" data-index="{{this.index}}">
{{#if parentNode}}
<span>{{parentNode.label}}</span>
{{else}}
<i class="fa fa-fast-backward"></i>
{{/if}}
</a>
{{#if parentNode}}<span>/</span>{{/if}}
</span>
{{/let}}
{{/let}}
</template>

View File

@ -0,0 +1,145 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
// TODO: use npm dependency
import transition from 'meteor/ohif:core/lib/third-party/transition-to-from-auto';
Template.selectTree.onRendered(() => {
const instance = Template.instance();
const component = instance.$('.select-tree:first').data('component');
const rootComponent = instance.data.root || component;
const rootInstance = rootComponent.templateInstance;
instance.component = component;
// Update the component's viewport height
instance.updateHeight = () => {
const height = rootInstance.$('.tree-options:last').data('height');
rootInstance.$('.tree-options:first').height(height);
};
// Update the opened node
instance.updateOpen = () => {
rootInstance.$('.select-tree').removeClass('open');
rootInstance.$('.select-tree:last').addClass('open');
};
// Put the component in selected state
instance.setSelected = (flag) => {
const $treeRoot = rootInstance.$('.select-tree-root:first').first();
const $firstChild = $treeRoot.children().first();
if (flag) {
const search = rootInstance.$('.tree-search').outerHeight() | 0;
const leaf = $treeRoot.find('.tree-leaf.active').position().top;
const martinTop = search + leaf;
// $firstChild.css('margin-top', -martinTop);
// $treeRoot.css('margin-top', martinTop).addClass('selected');
$treeRoot.addClass('selected');
} else {
// $treeRoot.removeClass('selected').css('margin-top', '');
// $firstChild.css('margin-top', '');
$treeRoot.removeClass('selected');
}
};
// Store the element's initial height
const $treeOptions = instance.$('.tree-options:first').first();
const height = $treeOptions.height();
$treeOptions.data('height', height);
instance.autorun(() => {
// Run this computation everytime the current node is changed
instance.data.currentNode.dep.depend();
// Update the viewport height
Tracker.afterFlush(() => {
instance.updateHeight();
instance.updateOpen();
});
});
const selector = '.select-tree:first>.tree-options>.tree-breadcrumb .path-link';
instance.$(selector).css('width', 0).each((index, element) => {
transition({
element: element,
prop: 'width',
style: 'width 0.3s ease',
val: 'auto'
});
});
});
Template.selectTree.events({
'change .select-tree:first>.tree-options>.tree-inputs>label>input'(event, instance) {
const component = instance.component;
const $target = $(event.target);
const $label = $target.closest('label');
const eventComponent = $target.data('component');
const rootComponent = instance.data.root || component;
const rootInstance = rootComponent.templateInstance;
// Change the component's value
component.value(eventComponent.value());
// Unset the active leaf
rootInstance.$('label').removeClass('active');
// Unset the selected state
instance.setSelected(false);
// Check if the clicked element is a node or a leaf
const isLeaf = $label.hasClass('tree-leaf');
if (isLeaf) {
// Change the active item
$label.addClass('active');
// Mark the component as selected
instance.setSelected(true);
} else {
// Get the position of the clicked element
const position = $label.position();
Meteor.defer(() => {
const $treeNode = $label.parent().siblings('.select-tree');
// Do the transition from the clicked position to top
$treeNode.css(position);
setTimeout(() => $treeNode.css('top', 0));
});
}
},
'click .select-tree:first>.tree-options>.tree-breadcrumb .tree-back'(event, instance) {
const rootComponent = instance.data.root || instance.component;
const rootInstance = rootComponent.templateInstance;
// Prevent the default element action
event.preventDefault();
// Reset the checked inputs to unchecked state
rootInstance.$('input').removeAttr('checked');
// Unset the selected state
instance.setSelected(false);
// Get the index of the breadcrumb's clicked option
const index = $(event.currentTarget).attr('data-index') | 0;
// Set the current instance
let currentInstance = instance.component.parent.templateInstance;
// Iterate over all parents until gets into the clicked node
for (let i = 1; i <= index; i++) {
// Unset the selected node
currentInstance.data.currentNode.set(null);
// Check if it's not the root node
if (i < index) {
// Change the current instance
currentInstance = currentInstance.component.parent.templateInstance;
}
}
}
});

View File

@ -0,0 +1,151 @@
/*!
* transition-to-from-auto 0.5.2
* https://github.com/75lb/transition-to-from-auto
* Copyright 2015 Lloyd Brookes <75pound@gmail.com>
*/
/**
@module
@alias transition
*/
(function(window, document){
"use strict";
var getComputedStyle = window.getComputedStyle;
var isTransition = "data-ttfaInTransition";
var elements = [];
var data = [];
// Transition detecting
var transitionProp = false;
var transitionEnd = false;
var testStyle = document.createElement("a").style;
var testProp;
if(testStyle[testProp = "webkitTransition"] !== undefined) {
transitionProp = testProp;
transitionEnd = testProp + "End";
}
if(testStyle[testProp = "transition"] !== undefined) {
transitionProp = testProp;
transitionEnd = testProp + "end";
}
function process(options, data) {
var el = options.element;
var val = options.val;
var prop = options.prop;
var style = el.style;
var startVal;
var autoVal;
if(!transitionProp) {
return style[prop] = val;
}
if(el.hasAttribute(isTransition)) {
el.removeEventListener(transitionEnd, data.l);
} else {
style[transitionProp] = "none";
startVal = getComputedStyle(el)[prop];
style[prop] = "auto";
autoVal = getComputedStyle(el)[prop];
// Interrupt
if(startVal === val || val === "auto" && startVal === autoVal) {
return;
}
data.auto = autoVal;
el.setAttribute(isTransition, 1);
// Transition
style[prop] = startVal;
el.offsetWidth;
style[transitionProp] = options.style;
}
style[prop] = val === "auto" ? data.auto : val;
data.l = function (e) {
if(e.propertyName === prop) {
el.removeAttribute(isTransition);
el.removeEventListener(transitionEnd, data.l);
if(val === "auto") {
/* avoid transition flashes in Safari */
style[transitionProp] = "none";
style[prop] = val;
}
}
};
el.addEventListener(transitionEnd, data.l);
}
/**
@param options {Object}
@param options.element {string | element} - The DOM element or selector to transition
@param options.val {string} - The value you want to transition to
@param [options.prop] {string} - The CSS property to transition, defaults to `"height"`
@param [options.style] {string} - The desired value for the `transition` CSS property (e.g. `"height 1s"`). If specified, this value is added inline and will override your CSS. Leave this value blank if you already have it defined in your stylesheet.
@alias module:transition-to-from-auto
*/
function transition(options){
var element = options.element;
var datum;
var index;
if(typeof element === "string") {
element = document.querySelector(element);
}
element = options.element = element instanceof Node ? element : false;
options.prop = options.prop || "height";
options.style = options.style || "";
if(element) {
index = elements.indexOf(element);
if(~index) {
datum = data[index];
} else {
datum = {};
elements.push(element);
data.push(datum);
}
process(options, datum);
}
}
/**
The name of the vendor-specific transition CSS property
@type {string}
@example
el.style[transition.prop + 'Duration'] = '1s';
*/
transition.prop = transitionProp;
/**
* The name of the [transition end event](https://developer.mozilla.org/en-US/docs/Web/Events/transitionend) in the current browser (typically `"transitionend"` or `"webkitTransitionEnd"`)
* @type {string}
* @example
* el.addEventListener(transition.end, function(){
* // the transition ended..
* });
*/
transition.end = transitionEnd;
if (typeof module === "object" && module.exports){
module.exports = transition;
} else if (typeof define === "function" && define.amd){
define(function(){
return transition;
});
} else {
window.transition = transition;
}
})(window, document);