more bits of read receipt animation implemented

pull/379/head
David Baker 2015-11-13 11:42:51 +00:00
parent 9a6624d1c7
commit bc2c744bed
4 changed files with 132 additions and 7 deletions

View File

@ -28,7 +28,6 @@
"filesize": "^3.1.2", "filesize": "^3.1.2",
"flux": "~2.0.3", "flux": "~2.0.3",
"linkifyjs": "^2.0.0-beta.4", "linkifyjs": "^2.0.0-beta.4",
"modernizr": "^3.1.0",
"matrix-js-sdk": "https://github.com/matrix-org/matrix-js-sdk.git#develop", "matrix-js-sdk": "https://github.com/matrix-org/matrix-js-sdk.git#develop",
"matrix-react-sdk": "^0.0.2", "matrix-react-sdk": "^0.0.2",
"modernizr": "^3.1.0", "modernizr": "^3.1.0",
@ -39,7 +38,8 @@
"react-dom": "^0.14.2", "react-dom": "^0.14.2",
"react-gemini-scrollbar": "^2.0.1", "react-gemini-scrollbar": "^2.0.1",
"react-loader": "^1.4.0", "react-loader": "^1.4.0",
"sanitize-html": "^1.0.0" "sanitize-html": "^1.0.0",
"velocity-animate": "^1.2.3"
}, },
"devDependencies": { "devDependencies": {
"babel": "^5.8.23", "babel": "^5.8.23",

83
src/Velociraptor.js Normal file
View File

@ -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 (
<span>
{childList}
</span>
);
},
});

View File

@ -49,7 +49,7 @@ module.exports = React.createClass({
initial = this.props.member.name[1].toUpperCase(); initial = this.props.member.name[1].toUpperCase();
return ( return (
<span className="mx_MemberAvatar" style={this.props.style}> <span className="mx_MemberAvatar" {...this.props}>
<span className="mx_MemberAvatar_initial" <span className="mx_MemberAvatar_initial"
style={{ fontSize: (this.props.width * 0.75) + "px", style={{ fontSize: (this.props.width * 0.75) + "px",
width: this.props.width + "px", width: this.props.width + "px",
@ -63,7 +63,7 @@ module.exports = React.createClass({
<img className="mx_MemberAvatar mx_MemberAvatar_image" src={this.state.imageUrl} <img className="mx_MemberAvatar mx_MemberAvatar_image" src={this.state.imageUrl}
onError={this.onError} onError={this.onError}
width={this.props.width} height={this.props.height} width={this.props.width} height={this.props.height}
style={this.props.style} {...this.props}
/> />
); );
} }

View File

@ -17,6 +17,7 @@ limitations under the License.
'use strict'; 'use strict';
var React = require('react'); var React = require('react');
var ReactDom = require('react-dom');
var classNames = require("classnames"); var classNames = require("classnames");
var sdk = require('matrix-react-sdk') var sdk = require('matrix-react-sdk')
@ -27,6 +28,8 @@ var ContextualMenu = require('../../../../ContextualMenu');
var TextForEvent = require('matrix-react-sdk/lib/TextForEvent'); var TextForEvent = require('matrix-react-sdk/lib/TextForEvent');
var Velociraptor = require('../../../../Velociraptor');
var eventTileTypes = { var eventTileTypes = {
'm.room.message': 'molecules.MessageTile', 'm.room.message': 'molecules.MessageTile',
'm.room.member' : 'molecules.EventAsTextTile', 'm.room.member' : 'molecules.EventAsTextTile',
@ -58,6 +61,10 @@ module.exports = React.createClass({
return {menu: false}; return {menu: false};
}, },
componentDidUpdate: function() {
this.readAvatarRect = ReactDom.findDOMNode(this.readAvatarNode).getBoundingClientRect();
},
onEditClicked: function(e) { onEditClicked: function(e) {
var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu'); var MessageContextMenu = sdk.getComponent('molecules.MessageContextMenu');
var buttonRect = e.target.getBoundingClientRect() var buttonRect = e.target.getBoundingClientRect()
@ -93,13 +100,42 @@ module.exports = React.createClass({
var left = 0; var left = 0;
var transitionOpts = {
duration: 1000,
easing: 'linear'
};
for (var i = 0; i < receipts.length; ++i) { for (var i = 0; i < receipts.length; ++i) {
var member = room.getMember(receipts[i].userId); 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) // add to the start so the most recent is on the end (ie. ends up rightmost)
avatars.unshift( avatars.unshift(
<MemberAvatar key={member.userId} member={member} <MemberAvatar key={member.userId} member={member}
width={14} height={14} resizeMethod="crop" width={14} height={14} resizeMethod="crop"
style={ {left: left} } style={startStyle}
enterTransition={enterTransitions}
enterTransitionOpts={enterTransitionOpts}
id={'mx_readAvatar'+member.userId}
/> />
); );
left -= 15; left -= 15;
@ -113,12 +149,18 @@ module.exports = React.createClass({
remText = <span className="mx_EventTile_readAvatarRemainder" style={ {left: left} }>+{ remainder }</span>; remText = <span className="mx_EventTile_readAvatarRemainder" style={ {left: left} }>+{ remainder }</span>;
} }
return <span className="mx_EventTile_readAvatars"> return <span className="mx_EventTile_readAvatars" ref={this.collectReadAvatarNode}>
{remText} {remText}
<Velociraptor transition={transitionOpts}>
{avatars} {avatars}
</Velociraptor>
</span>; </span>;
}, },
collectReadAvatarNode: function(node) {
this.readAvatarNode = node;
},
render: function() { render: function() {
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp'); var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
var SenderProfile = sdk.getComponent('molecules.SenderProfile'); var SenderProfile = sdk.getComponent('molecules.SenderProfile');