Merge pull request #85 from matrix-org/read_marker_animate
Improve read markers so they show at appropriate times and animate away.pull/21833/head
commit
2eb724f1f0
|
@ -31,6 +31,11 @@ class UserActivity {
|
||||||
start() {
|
start() {
|
||||||
document.onmousemove = this._onUserActivity.bind(this);
|
document.onmousemove = this._onUserActivity.bind(this);
|
||||||
document.onkeypress = this._onUserActivity.bind(this);
|
document.onkeypress = this._onUserActivity.bind(this);
|
||||||
|
// can't use document.scroll here because that's only the document
|
||||||
|
// itself being scrolled. Need to use addEventListener's useCapture.
|
||||||
|
// also this needs to be the wheel event, not scroll, as scroll is
|
||||||
|
// fired when the view scrolls down for a new message.
|
||||||
|
window.addEventListener('wheel', this._onUserActivity.bind(this), true);
|
||||||
this.lastActivityAtTs = new Date().getTime();
|
this.lastActivityAtTs = new Date().getTime();
|
||||||
this.lastDispatchAtTs = 0;
|
this.lastDispatchAtTs = 0;
|
||||||
}
|
}
|
||||||
|
@ -41,10 +46,11 @@ class UserActivity {
|
||||||
stop() {
|
stop() {
|
||||||
document.onmousemove = undefined;
|
document.onmousemove = undefined;
|
||||||
document.onkeypress = undefined;
|
document.onkeypress = undefined;
|
||||||
|
window.removeEventListener('wheel', this._onUserActivity.bind(this), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUserActivity(event) {
|
_onUserActivity(event) {
|
||||||
if (event.screenX) {
|
if (event.screenX && event.type == "mousemove") {
|
||||||
if (event.screenX === this.lastScreenX &&
|
if (event.screenX === this.lastScreenX &&
|
||||||
event.screenY === this.lastScreenY)
|
event.screenY === this.lastScreenY)
|
||||||
{
|
{
|
||||||
|
|
|
@ -40,6 +40,7 @@ var dis = require("../../dispatcher");
|
||||||
|
|
||||||
var PAGINATE_SIZE = 20;
|
var PAGINATE_SIZE = 20;
|
||||||
var INITIAL_SIZE = 20;
|
var INITIAL_SIZE = 20;
|
||||||
|
var SEND_READ_RECEIPT_DELAY = 2000;
|
||||||
|
|
||||||
var DEBUG_SCROLL = false;
|
var DEBUG_SCROLL = false;
|
||||||
|
|
||||||
|
@ -74,6 +75,8 @@ module.exports = React.createClass({
|
||||||
syncState: MatrixClientPeg.get().getSyncState(),
|
syncState: MatrixClientPeg.get().getSyncState(),
|
||||||
hasUnsentMessages: this._hasUnsentMessages(room),
|
hasUnsentMessages: this._hasUnsentMessages(room),
|
||||||
callState: null,
|
callState: null,
|
||||||
|
readMarkerEventId: room.getEventReadUpTo(MatrixClientPeg.get().credentials.userId),
|
||||||
|
readMarkerGhostEventId: undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -261,7 +264,33 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
onRoomReceipt: function(receiptEvent, room) {
|
onRoomReceipt: function(receiptEvent, room) {
|
||||||
if (room.roomId == this.props.roomId) {
|
if (room.roomId == this.props.roomId) {
|
||||||
this.forceUpdate();
|
var readMarkerEventId = this.state.room.getEventReadUpTo(MatrixClientPeg.get().credentials.userId);
|
||||||
|
var readMarkerGhostEventId = this.state.readMarkerGhostEventId;
|
||||||
|
if (this.state.readMarkerEventId !== undefined && this.state.readMarkerEventId != readMarkerEventId) {
|
||||||
|
readMarkerGhostEventId = this.state.readMarkerEventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if the event after the one referenced in the read receipt if sent by us, do nothing since
|
||||||
|
// this is a temporary period before the synthesized receipt for our own message arrives
|
||||||
|
var readMarkerGhostEventIndex;
|
||||||
|
for (var i = 0; i < room.timeline.length; ++i) {
|
||||||
|
if (room.timeline[i].getId() == readMarkerGhostEventId) {
|
||||||
|
readMarkerGhostEventIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (readMarkerGhostEventIndex + 1 < room.timeline.length) {
|
||||||
|
var nextEvent = room.timeline[readMarkerGhostEventIndex + 1];
|
||||||
|
if (nextEvent.sender && nextEvent.sender.userId == MatrixClientPeg.get().credentials.userId) {
|
||||||
|
readMarkerGhostEventId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
readMarkerEventId: readMarkerEventId,
|
||||||
|
readMarkerGhostEventId: readMarkerGhostEventId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -682,10 +711,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var EventTile = sdk.getComponent('rooms.EventTile');
|
var EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
|
|
||||||
|
|
||||||
var prevEvent = null; // the last event we showed
|
var prevEvent = null; // the last event we showed
|
||||||
var readReceiptEventId = this.state.room.getEventReadUpTo(MatrixClientPeg.get().credentials.userId);
|
|
||||||
var startIdx = Math.max(0, this.state.room.timeline.length - this.state.messageCap);
|
var startIdx = Math.max(0, this.state.room.timeline.length - this.state.messageCap);
|
||||||
|
var readMarkerIndex;
|
||||||
|
var ghostIndex;
|
||||||
for (var i = startIdx; i < this.state.room.timeline.length; i++) {
|
for (var i = startIdx; i < this.state.room.timeline.length; i++) {
|
||||||
var mxEv = this.state.room.timeline[i];
|
var mxEv = this.state.room.timeline[i];
|
||||||
|
|
||||||
|
@ -699,6 +728,25 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now we've decided whether or not to show this message,
|
||||||
|
// add the read up to marker if appropriate
|
||||||
|
// doing this here means we implicitly do not show the marker
|
||||||
|
// if it's at the bottom
|
||||||
|
// NB. it would be better to decide where the read marker was going
|
||||||
|
// when the state changed rather than here in the render method, but
|
||||||
|
// this is where we decide what messages we show so it's the only
|
||||||
|
// place we know whether we're at the bottom or not.
|
||||||
|
var self = this;
|
||||||
|
var mxEvSender = mxEv.sender ? mxEv.sender.userId : null;
|
||||||
|
if (prevEvent && prevEvent.getId() == this.state.readMarkerEventId && mxEvSender != MatrixClientPeg.get().credentials.userId) {
|
||||||
|
var hr;
|
||||||
|
hr = (<hr className="mx_RoomView_myReadMarker" style={{opacity: 1, width: '99%'}} ref={function(n) {
|
||||||
|
self.readMarkerNode = n;
|
||||||
|
}} />);
|
||||||
|
readMarkerIndex = ret.length;
|
||||||
|
ret.push(<li key="_readupto" className="mx_RoomView_myReadMarker_container">{hr}</li>);
|
||||||
|
}
|
||||||
|
|
||||||
// is this a continuation of the previous message?
|
// is this a continuation of the previous message?
|
||||||
var continuation = false;
|
var continuation = false;
|
||||||
if (prevEvent !== null) {
|
if (prevEvent !== null) {
|
||||||
|
@ -735,13 +783,29 @@ module.exports = React.createClass({
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (eventId == readReceiptEventId) {
|
// A read up to marker has died and returned as a ghost!
|
||||||
ret.push(<hr className="mx_RoomView_myReadMarker" />);
|
// Lives in the dom as the ghost of the previous one while it fades away
|
||||||
|
if (eventId == this.state.readMarkerGhostEventId) {
|
||||||
|
ghostIndex = ret.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
prevEvent = mxEv;
|
prevEvent = mxEv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// splice the read marker ghost in now that we know whether the read receipt
|
||||||
|
// is the last element or not, because we only decide as we're going along.
|
||||||
|
if (readMarkerIndex === undefined && ghostIndex && ghostIndex <= ret.length) {
|
||||||
|
var hr;
|
||||||
|
hr = (<hr className="mx_RoomView_myReadMarker" style={{opacity: 1, width: '85%'}} ref={function(n) {
|
||||||
|
Velocity(n, {opacity: '0', width: '10%'}, {duration: 400, easing: 'easeInSine', delay: 1000, complete: function() {
|
||||||
|
self.setState({readMarkerGhostEventId: undefined});
|
||||||
|
}});
|
||||||
|
}} />);
|
||||||
|
ret.splice(ghostIndex, 0, (
|
||||||
|
<li key="_readuptoghost" className="mx_RoomView_myReadMarker_container">{hr}</li>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue