diff --git a/package.json b/package.json
index d71fdac5..7151aef8 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,6 @@
"filesize": "^3.1.2",
"flux": "~2.0.3",
"linkifyjs": "^2.0.0-beta.4",
- "modernizr": "^3.1.0",
"matrix-js-sdk": "https://github.com/matrix-org/matrix-js-sdk.git#develop",
"matrix-react-sdk": "^0.0.2",
"modernizr": "^3.1.0",
@@ -39,7 +38,8 @@
"react-dom": "^0.14.2",
"react-gemini-scrollbar": "^2.0.1",
"react-loader": "^1.4.0",
- "sanitize-html": "^1.0.0"
+ "sanitize-html": "^1.0.0",
+ "velocity-animate": "^1.2.3"
},
"devDependencies": {
"babel": "^5.8.23",
diff --git a/src/Velociraptor.js b/src/Velociraptor.js
new file mode 100644
index 00000000..1a14381d
--- /dev/null
+++ b/src/Velociraptor.js
@@ -0,0 +1,83 @@
+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);
+ }
+ self.children[c.key] = old;
+ } else {
+ self.children[c.key] = c;
+ }
+ });
+ },
+
+ collectNode: function(k, node) {
+ if (
+ this.nodes[k] === undefined &&
+ node.props.enterTransition &&
+ Object.keys(node.props.enterTransition).length
+ ) {
+ var domNode = ReactDom.findDOMNode(node);
+ var transitions = node.props.enterTransition;
+ var transitionOpts = node.props.enterTransitionOpts;
+ if (!Array.isArray(transitions)) {
+ transitions = [ transitions ];
+ transitionOpts = [ transitionOpts ];
+ }
+ for (var i = 0; i < transitions.length; ++i) {
+ Velocity(domNode, transitions[i], transitionOpts[i]);
+ console.log("enter: "+JSON.stringify(transitions[i]));
+ }
+ }
+ 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/skins/vector/views/atoms/MemberAvatar.js b/src/skins/vector/views/atoms/MemberAvatar.js
index e7d6b65d..9d632d72 100644
--- a/src/skins/vector/views/atoms/MemberAvatar.js
+++ b/src/skins/vector/views/atoms/MemberAvatar.js
@@ -49,7 +49,7 @@ module.exports = React.createClass({
initial = this.props.member.name[1].toUpperCase();
return (
-
+
);
}
diff --git a/src/skins/vector/views/molecules/EventTile.js b/src/skins/vector/views/molecules/EventTile.js
index 695240d0..e99f227f 100644
--- a/src/skins/vector/views/molecules/EventTile.js
+++ b/src/skins/vector/views/molecules/EventTile.js
@@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var React = require('react');
+var ReactDom = require('react-dom');
var classNames = require("classnames");
var sdk = require('matrix-react-sdk')
@@ -27,6 +28,8 @@ var ContextualMenu = require('../../../../ContextualMenu');
var TextForEvent = require('matrix-react-sdk/lib/TextForEvent');
+var Velociraptor = require('../../../../Velociraptor');
+
var eventTileTypes = {
'm.room.message': 'molecules.MessageTile',
'm.room.member' : 'molecules.EventAsTextTile',
@@ -58,6 +61,10 @@ module.exports = React.createClass({
return {menu: false};
},
+ componentDidUpdate: function() {
+ this.readAvatarRect = ReactDom.findDOMNode(this.readAvatarNode).getBoundingClientRect();
+ },
+
onEditClicked: function(e) {
var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu');
var buttonRect = e.target.getBoundingClientRect()
@@ -93,13 +100,42 @@ module.exports = React.createClass({
var left = 0;
+ var transitionOpts = {
+ duration: 1000,
+ easing: 'linear'
+ };
+
for (var i = 0; i < receipts.length; ++i) {
var member = room.getMember(receipts[i].userId);
+
+ // Using react refs here would mean both getting Velociraptor to expose
+ // them and making them scoped to the whole RoomView. Not impossible, but
+ // getElementById seems simpler at least for a first cut.
+ var oldAvatarDomNode = document.getElementById('mx_readAvatar'+member.userId);
+ var startStyle = { left: left+'px' };
+ var enterTransitions = [];
+ var enterTransitionOpts = [];
+ if (oldAvatarDomNode && this.readAvatarRect) {
+ var oldRect = oldAvatarDomNode.getBoundingClientRect();
+ startStyle.top = oldRect.top - this.readAvatarRect.top;
+
+ if (oldAvatarDomNode.style.left !== '0px') {
+ startStyle.left = oldAvatarDomNode.style.left;
+ enterTransitions.push({ left: left+'px' });
+ enterTransitionOpts.push(transitionOpts);
+ }
+ enterTransitions.push({ top: '0px' });
+ enterTransitionOpts.push(transitionOpts);
+ }
+
// add to the start so the most recent is on the end (ie. ends up rightmost)
avatars.unshift(
);
left -= 15;
@@ -113,12 +149,18 @@ module.exports = React.createClass({
remText = +{ remainder };
}
- return
+ return
{remText}
- {avatars}
+
+ {avatars}
+
;
},
+ collectReadAvatarNode: function(node) {
+ this.readAvatarNode = node;
+ },
+
render: function() {
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
var SenderProfile = sdk.getComponent('molecules.SenderProfile');