mirror of
https://github.com/element-hq/element-web
synced 2024-11-22 17:25:50 +03:00
more bits of read receipt animation implemented
This commit is contained in:
parent
9a6624d1c7
commit
bc2c744bed
4 changed files with 132 additions and 7 deletions
|
@ -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",
|
||||
|
|
83
src/Velociraptor.js
Normal file
83
src/Velociraptor.js
Normal 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>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -49,7 +49,7 @@ module.exports = React.createClass({
|
|||
initial = this.props.member.name[1].toUpperCase();
|
||||
|
||||
return (
|
||||
<span className="mx_MemberAvatar" style={this.props.style}>
|
||||
<span className="mx_MemberAvatar" {...this.props}>
|
||||
<span className="mx_MemberAvatar_initial"
|
||||
style={{ fontSize: (this.props.width * 0.75) + "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}
|
||||
onError={this.onError}
|
||||
width={this.props.width} height={this.props.height}
|
||||
style={this.props.style}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
<MemberAvatar key={member.userId} member={member}
|
||||
width={14} height={14} resizeMethod="crop"
|
||||
style={ {left: left} }
|
||||
style={startStyle}
|
||||
enterTransition={enterTransitions}
|
||||
enterTransitionOpts={enterTransitionOpts}
|
||||
id={'mx_readAvatar'+member.userId}
|
||||
/>
|
||||
);
|
||||
left -= 15;
|
||||
|
@ -113,12 +149,18 @@ module.exports = React.createClass({
|
|||
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}
|
||||
{avatars}
|
||||
<Velociraptor transition={transitionOpts}>
|
||||
{avatars}
|
||||
</Velociraptor>
|
||||
</span>;
|
||||
},
|
||||
|
||||
collectReadAvatarNode: function(node) {
|
||||
this.readAvatarNode = node;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var MessageTimestamp = sdk.getComponent('atoms.MessageTimestamp');
|
||||
var SenderProfile = sdk.getComponent('molecules.SenderProfile');
|
||||
|
|
Loading…
Reference in a new issue