Files
ThreadNet-Web/src/components/views/rooms/ReadReceiptMarker.js
T

226 lines
7.8 KiB
JavaScript
Raw Normal View History

2016-04-20 23:03:05 +01:00
/*
Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
2016-04-20 23:03:05 +01:00
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {createRef} from 'react';
2017-12-26 14:03:18 +13:00
import PropTypes from 'prop-types';
import '../../../VelocityBounce';
2017-06-08 14:08:51 +01:00
import { _t } from '../../../languageHandler';
2018-01-10 12:00:11 +00:00
import {formatDate} from '../../../DateUtils';
2019-12-20 14:41:07 -07:00
import Velociraptor from "../../../Velociraptor";
import * as sdk from "../../../index";
2020-06-08 14:45:12 +01:00
import {toPx} from "../../../utils/units";
2017-10-11 17:56:17 +01:00
let bounce = false;
2016-04-20 23:03:05 +01:00
try {
if (global.localStorage) {
bounce = global.localStorage.getItem('avatar_bounce') == 'true';
}
} catch (e) {
}
2021-02-26 22:25:50 -07:00
export default class ReadReceiptMarker extends React.PureComponent {
2020-08-29 12:14:16 +01:00
static propTypes = {
2016-04-20 23:03:05 +01:00
// the RoomMember to show the RR for
member: PropTypes.object,
// userId to fallback the avatar to
// if the member hasn't been loaded yet
fallbackUserId: PropTypes.string.isRequired,
2016-04-20 23:03:05 +01:00
// number of pixels to offset the avatar from the right of its parent;
// typically a negative value.
2017-12-26 14:03:18 +13:00
leftOffset: PropTypes.number,
2016-04-20 23:03:05 +01:00
// true to hide the avatar (it will still be animated)
2017-12-26 14:03:18 +13:00
hidden: PropTypes.bool,
2016-04-20 23:03:05 +01:00
// don't animate this RR into position
2017-12-26 14:03:18 +13:00
suppressAnimation: PropTypes.bool,
2016-04-20 23:03:05 +01:00
// an opaque object for storing information about this user's RR in
// this room
2017-12-26 14:03:18 +13:00
readReceiptInfo: PropTypes.object,
2016-04-20 23:03:05 +01:00
2016-04-22 17:03:15 +01:00
// A function which is used to check if the parent panel is being
// unmounted, to avoid unnecessary work. Should return true if we
// are being unmounted.
2017-12-26 14:03:18 +13:00
checkUnmounting: PropTypes.func,
2016-04-22 17:03:15 +01:00
2016-04-20 23:03:05 +01:00
// callback for clicks on this RR
2017-12-26 14:03:18 +13:00
onClick: PropTypes.func,
// Timestamp when the receipt was read
2017-12-26 14:03:18 +13:00
timestamp: PropTypes.number,
2017-06-22 08:53:58 -06:00
// True to show twelve hour format, false otherwise
2017-12-26 14:03:18 +13:00
showTwelveHour: PropTypes.bool,
2020-08-29 12:14:16 +01:00
};
2016-04-20 23:03:05 +01:00
2020-08-29 12:14:16 +01:00
static defaultProps = {
leftOffset: 0,
};
2016-04-20 23:03:05 +01:00
2020-08-29 12:14:16 +01:00
constructor(props) {
super(props);
this._avatar = createRef();
this.state = {
// if we are going to animate the RR, we don't show it on first render,
// and instead just add a placeholder to the DOM; once we've been
// mounted, we start an animation which moves the RR from its old
// position.
2016-04-20 23:03:05 +01:00
suppressDisplay: !this.props.suppressAnimation,
2017-01-20 14:22:27 +00:00
};
2020-08-29 12:14:16 +01:00
}
2016-04-20 23:03:05 +01:00
2020-08-29 12:14:16 +01:00
componentWillUnmount() {
2016-04-20 23:03:05 +01:00
// before we remove the rr, store its location in the map, so that if
// it reappears, it can be animated from the right place.
2017-10-11 17:56:17 +01:00
const rrInfo = this.props.readReceiptInfo;
2016-04-20 23:03:05 +01:00
if (!rrInfo) {
return;
}
2016-04-22 17:03:15 +01:00
// checking the DOM properties can force a re-layout, which can be
// quite expensive; so if the parent messagepanel is being unmounted,
// then don't bother with this.
if (this.props.checkUnmounting && this.props.checkUnmounting()) {
return;
}
const avatarNode = this._avatar.current;
2016-04-20 23:03:05 +01:00
rrInfo.top = avatarNode.offsetTop;
rrInfo.left = avatarNode.offsetLeft;
rrInfo.parent = avatarNode.offsetParent;
2020-08-29 12:14:16 +01:00
}
2016-04-20 23:03:05 +01:00
2020-08-29 12:14:16 +01:00
componentDidMount() {
2016-04-20 23:03:05 +01:00
if (!this.state.suppressDisplay) {
// we've already done our display - nothing more to do.
return;
}
// treat new RRs as though they were off the top of the screen
2017-10-11 17:56:17 +01:00
let oldTop = -15;
2016-04-20 23:03:05 +01:00
2017-10-11 17:56:17 +01:00
const oldInfo = this.props.readReceiptInfo;
2016-04-20 23:03:05 +01:00
if (oldInfo && oldInfo.parent) {
oldTop = oldInfo.top + oldInfo.parent.getBoundingClientRect().top;
}
const newElement = this._avatar.current;
let startTopOffset;
if (!newElement.offsetParent) {
// this seems to happen sometimes for reasons I don't understand
// the docs for `offsetParent` say it may be null if `display` is
// `none`, but I can't see why that would happen.
console.warn(
`ReadReceiptMarker for ${this.props.fallbackUserId} in has no offsetParent`,
);
startTopOffset = 0;
} else {
startTopOffset = oldTop - newElement.offsetParent.getBoundingClientRect().top;
}
2016-04-20 23:03:05 +01:00
2017-10-11 17:56:17 +01:00
const startStyles = [];
const enterTransitionOpts = [];
2016-04-20 23:03:05 +01:00
if (oldInfo && oldInfo.left) {
// start at the old height and in the old h pos
startStyles.push({ top: startTopOffset+"px",
2020-06-08 14:45:12 +01:00
left: toPx(oldInfo.left) });
2016-04-20 23:03:05 +01:00
2017-10-11 17:56:17 +01:00
const reorderTransitionOpts = {
2016-04-20 23:03:05 +01:00
duration: 100,
easing: 'easeOut',
2016-04-20 23:03:05 +01:00
};
enterTransitionOpts.push(reorderTransitionOpts);
}
// then shift to the rightmost column,
// and then it will drop down to its resting position
2021-02-26 22:24:36 -07:00
//
// XXX: We use a `left: 1px` to trick velocity-animate into actually animating. This
// is a very annoying bug where if it thinks there's no change to `left` then it'll
// skip applying it, thus making our read receipt at +14px instead of +0px like it
// should be. This does cause 1px of drift for read receipts, however nobody should
// notice this while it's also falling.
startStyles.push({ top: startTopOffset+'px', left: '1px' });
2016-04-20 23:03:05 +01:00
enterTransitionOpts.push({
duration: bounce ? Math.min(Math.log(Math.abs(startTopOffset)) * 200, 3000) : 300,
easing: bounce ? 'easeOutBounce' : 'easeOutCubic',
});
this.setState({
suppressDisplay: false,
startStyles: startStyles,
enterTransitionOpts: enterTransitionOpts,
});
2020-08-29 12:14:16 +01:00
}
2016-04-20 23:03:05 +01:00
2020-08-29 12:14:16 +01:00
render() {
2017-10-11 17:56:17 +01:00
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
2016-04-20 23:03:05 +01:00
if (this.state.suppressDisplay) {
2019-12-23 12:54:31 +00:00
return <div ref={this._avatar} />;
2016-04-20 23:03:05 +01:00
}
2017-10-11 17:56:17 +01:00
const style = {
2020-06-08 14:45:12 +01:00
left: toPx(this.props.leftOffset),
2016-04-20 23:03:05 +01:00
top: '0px',
visibility: this.props.hidden ? 'hidden' : 'visible',
};
let title;
if (this.props.timestamp) {
2018-03-23 17:14:29 +13:00
const dateString = formatDate(new Date(this.props.timestamp), this.props.showTwelveHour);
if (!this.props.member || this.props.fallbackUserId === this.props.member.rawDisplayName) {
2018-03-23 17:14:29 +13:00
title = _t(
"Seen by %(userName)s at %(dateTime)s",
{userName: this.props.fallbackUserId,
2018-03-23 17:14:29 +13:00
dateTime: dateString},
);
2018-03-26 13:33:47 +01:00
} else {
2018-03-23 17:14:29 +13:00
title = _t(
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s",
{displayName: this.props.member.rawDisplayName,
userName: this.props.fallbackUserId,
2018-03-23 17:14:29 +13:00
dateTime: dateString},
);
}
}
2016-04-20 23:03:05 +01:00
return (
<Velociraptor
startStyles={this.state.startStyles}
enterTransitionOpts={this.state.enterTransitionOpts} >
2016-04-20 23:03:05 +01:00
<MemberAvatar
member={this.props.member}
fallbackUserId={this.props.fallbackUserId}
aria-hidden="true"
2016-04-20 23:03:05 +01:00
width={14} height={14} resizeMethod="crop"
style={style}
title={title}
2017-02-02 17:36:26 +00:00
onClick={this.props.onClick}
inputRef={this._avatar}
2016-04-20 23:03:05 +01:00
/>
</Velociraptor>
);
2020-08-29 12:14:16 +01:00
}
}