more bits of read receipt animation implemented
parent
9a6624d1c7
commit
bc2c744bed
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
|
Loading…
Reference in New Issue