diff --git a/package.json b/package.json index fb7558ad5..bc6e3496c 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "filesize": "^3.1.2", "flux": "~2.0.3", "linkifyjs": "^2.0.0-beta.4", - "matrix-js-sdk": "^0.3.0", - "matrix-react-sdk": "^0.0.2", + "matrix-js-sdk": "https://github.com/matrix-org/matrix-js-sdk.git#develop", + "matrix-react-sdk": "https://github.com/matrix-org/matrix-react-sdk.git#develop", "modernizr": "^3.1.0", "q": "^1.4.1", "react": "^0.14.2", @@ -37,6 +37,7 @@ "react-dnd-html5-backend": "^2.0.0", "react-dom": "^0.14.2", "react-gemini-scrollbar": "^2.0.1", + "velocity-animate": "^1.2.3", "sanitize-html": "^1.0.0" }, "devDependencies": { diff --git a/src/ContextualMenu.js b/src/ContextualMenu.js index 3327aa948..a7b1849e1 100644 --- a/src/ContextualMenu.js +++ b/src/ContextualMenu.js @@ -43,7 +43,7 @@ module.exports = { var self = this; var closeMenu = function() { - React.unmountComponentAtNode(self.getOrCreateContainer()); + ReactDOM.unmountComponentAtNode(self.getOrCreateContainer()); if (props && props.onFinished) props.onFinished.apply(null, arguments); }; diff --git a/src/Velociraptor.js b/src/Velociraptor.js new file mode 100644 index 000000000..df6b3c95d --- /dev/null +++ b/src/Velociraptor.js @@ -0,0 +1,113 @@ +var React = require('react'); +var ReactDom = require('react-dom'); +var Velocity = require('velocity-animate'); + +/** + * The Velociraptor contains components and animates transitions with velocity. + * It will only pick up direct changes to properties ('left', currently), and so + * will not work for animating positional changes where the position is implicit + * from DOM order. This makes it a lot simpler and lighter: if you need fully + * automatic positional animation, look at react-shuffle or similar libraries. + */ +module.exports = React.createClass({ + displayName: 'Velociraptor', + + propTypes: { + children: React.PropTypes.array, + transition: React.PropTypes.object, + container: React.PropTypes.string + }, + + componentWillMount: function() { + this.children = {}; + this.nodes = {}; + var self = this; + React.Children.map(this.props.children, function(c) { + self.children[c.props.key] = c; + }); + }, + + componentWillReceiveProps: function(nextProps) { + var self = this; + var oldChildren = this.children; + this.children = {}; + React.Children.map(nextProps.children, function(c) { + if (oldChildren[c.key]) { + var old = oldChildren[c.key]; + var oldNode = ReactDom.findDOMNode(self.nodes[old.key]); + + if (oldNode.style.left != c.props.style.left) { + Velocity(oldNode, { left: c.props.style.left }, self.props.transition).then(function() { + // special case visibility because it's nonsensical to animate an invisible element + // so we always hidden->visible pre-transition and visible->hidden after + if (oldNode.style.visibility == 'visible' && c.props.style.visibility == 'hidden') { + oldNode.style.visibility = c.props.style.visibility; + } + }); + if (oldNode.style.visibility == 'hidden' && c.props.style.visibility == 'visible') { + oldNode.style.visibility = c.props.style.visibility; + } + //console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left); + } + self.children[c.key] = old; + } else { + // new element. If it has a startStyle, use that as the style and go through + // the enter animations + var newProps = { + ref: self.collectNode.bind(self, c.key) + }; + if (c.props.startStyle && Object.keys(c.props.startStyle).length) { + var startStyle = c.props.startStyle; + if (Array.isArray(startStyle)) { + startStyle = startStyle[0]; + } + newProps._restingStyle = c.props.style; + newProps.style = startStyle; + //console.log("mounted@startstyle0: "+JSON.stringify(startStyle)); + // apply the enter animations once it's mounted + } + self.children[c.key] = React.cloneElement(c, newProps); + } + }); + }, + + collectNode: function(k, node) { + if ( + this.nodes[k] === undefined && + node.props.startStyle && + Object.keys(node.props.startStyle).length + ) { + var domNode = ReactDom.findDOMNode(node); + var startStyles = node.props.startStyle; + var transitionOpts = node.props.enterTransitionOpts; + if (!Array.isArray(startStyles)) { + startStyles = [ startStyles ]; + transitionOpts = [ transitionOpts ]; + } + // start from startStyle 1: 0 is the one we gave it + // to start with, so now we animate 1 etc. + for (var i = 1; i < startStyles.length; ++i) { + Velocity(domNode, startStyles[i], transitionOpts[i-1]); + //console.log("start: "+JSON.stringify(startStyles[i])); + } + // and then we animate to the resting state + Velocity(domNode, node.props._restingStyle, transitionOpts[i-1]); + //console.log("enter: "+JSON.stringify(node.props._restingStyle)); + } + this.nodes[k] = node; + }, + + render: function() { + var self = this; + var childList = Object.keys(this.children).map(function(k) { + return React.cloneElement(self.children[k], { + ref: self.collectNode.bind(self, self.children[k].key) + }); + }); + return ( + + {childList} + + ); + }, +}); diff --git a/src/VelocityBounce.js b/src/VelocityBounce.js new file mode 100644 index 000000000..c85aa254f --- /dev/null +++ b/src/VelocityBounce.js @@ -0,0 +1,15 @@ +var Velocity = require('velocity-animate'); + +// courtesy of https://github.com/julianshapiro/velocity/issues/283 +// We only use easeOutBounce (easeInBounce is just sort of nonsensical) +function bounce( p ) { + var pow2, + bounce = 4; + + while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {} + return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); +} + +Velocity.Easings.easeOutBounce = function(p) { + return 1 - bounce(1 - p); +} diff --git a/src/components/login/Login.js b/src/components/login/Login.js index 414528f1b..80d0314fc 100644 --- a/src/components/login/Login.js +++ b/src/components/login/Login.js @@ -165,11 +165,11 @@ module.exports = React.createClass({displayName: 'Login',