Merge pull request #3656 from matrix-org/dbkr/messagepanel_react_class

Convert MessagePanel to React class
This commit is contained in:
David Baker 2019-11-22 13:19:57 +00:00 committed by GitHub
commit ee40c7891b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,6 +1,7 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -18,7 +19,6 @@ limitations under the License.
/* global Velocity */ /* global Velocity */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
@ -37,10 +37,8 @@ const isMembershipChange = (e) => e.getType() === 'm.room.member' || e.getType()
/* (almost) stateless UI component which builds the event tiles in the room timeline. /* (almost) stateless UI component which builds the event tiles in the room timeline.
*/ */
module.exports = createReactClass({ export default class MessagePanel extends React.Component {
displayName: 'MessagePanel', static propTypes = {
propTypes: {
// true to give the component a 'display: none' style. // true to give the component a 'display: none' style.
hidden: PropTypes.bool, hidden: PropTypes.bool,
@ -109,9 +107,10 @@ module.exports = createReactClass({
// whether to show reactions for an event // whether to show reactions for an event
showReactions: PropTypes.bool, showReactions: PropTypes.bool,
}, };
componentWillMount: function() { constructor() {
super();
// the event after which we put a visible unread marker on the last // the event after which we put a visible unread marker on the last
// render cycle; null if readMarkerVisible was false or the RM was // render cycle; null if readMarkerVisible was false or the RM was
// suppressed (eg because it was at the end of the timeline) // suppressed (eg because it was at the end of the timeline)
@ -167,38 +166,42 @@ module.exports = createReactClass({
this._showHiddenEventsInTimeline = this._showHiddenEventsInTimeline =
SettingsStore.getValue("showHiddenEventsInTimeline"); SettingsStore.getValue("showHiddenEventsInTimeline");
this._isMounted = true;
},
componentWillUnmount: function() {
this._isMounted = false; this._isMounted = false;
}, }
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
/* get the DOM node representing the given event */ /* get the DOM node representing the given event */
getNodeForEventId: function(eventId) { getNodeForEventId(eventId) {
if (!this.eventNodes) { if (!this.eventNodes) {
return undefined; return undefined;
} }
return this.eventNodes[eventId]; return this.eventNodes[eventId];
}, }
/* return true if the content is fully scrolled down right now; else false. /* return true if the content is fully scrolled down right now; else false.
*/ */
isAtBottom: function() { isAtBottom() {
return this.refs.scrollPanel return this.refs.scrollPanel
&& this.refs.scrollPanel.isAtBottom(); && this.refs.scrollPanel.isAtBottom();
}, }
/* get the current scroll state. See ScrollPanel.getScrollState for /* get the current scroll state. See ScrollPanel.getScrollState for
* details. * details.
* *
* returns null if we are not mounted. * returns null if we are not mounted.
*/ */
getScrollState: function() { getScrollState() {
if (!this.refs.scrollPanel) { return null; } if (!this.refs.scrollPanel) { return null; }
return this.refs.scrollPanel.getScrollState(); return this.refs.scrollPanel.getScrollState();
}, }
// returns one of: // returns one of:
// //
@ -206,7 +209,7 @@ module.exports = createReactClass({
// -1: read marker is above the window // -1: read marker is above the window
// 0: read marker is within the window // 0: read marker is within the window
// +1: read marker is below the window // +1: read marker is below the window
getReadMarkerPosition: function() { getReadMarkerPosition() {
const readMarker = this.refs.readMarkerNode; const readMarker = this.refs.readMarkerNode;
const messageWrapper = this.refs.scrollPanel; const messageWrapper = this.refs.scrollPanel;
@ -226,45 +229,45 @@ module.exports = createReactClass({
} else { } else {
return 1; return 1;
} }
}, }
/* jump to the top of the content. /* jump to the top of the content.
*/ */
scrollToTop: function() { scrollToTop() {
if (this.refs.scrollPanel) { if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollToTop(); this.refs.scrollPanel.scrollToTop();
} }
}, }
/* jump to the bottom of the content. /* jump to the bottom of the content.
*/ */
scrollToBottom: function() { scrollToBottom() {
if (this.refs.scrollPanel) { if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollToBottom(); this.refs.scrollPanel.scrollToBottom();
} }
}, }
/** /**
* Page up/down. * Page up/down.
* *
* @param {number} mult: -1 to page up, +1 to page down * @param {number} mult: -1 to page up, +1 to page down
*/ */
scrollRelative: function(mult) { scrollRelative(mult) {
if (this.refs.scrollPanel) { if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollRelative(mult); this.refs.scrollPanel.scrollRelative(mult);
} }
}, }
/** /**
* Scroll up/down in response to a scroll key * Scroll up/down in response to a scroll key
* *
* @param {KeyboardEvent} ev: the keyboard event to handle * @param {KeyboardEvent} ev: the keyboard event to handle
*/ */
handleScrollKey: function(ev) { handleScrollKey(ev) {
if (this.refs.scrollPanel) { if (this.refs.scrollPanel) {
this.refs.scrollPanel.handleScrollKey(ev); this.refs.scrollPanel.handleScrollKey(ev);
} }
}, }
/* jump to the given event id. /* jump to the given event id.
* *
@ -276,33 +279,33 @@ module.exports = createReactClass({
* node (specifically, the bottom of it) will be positioned. If omitted, it * node (specifically, the bottom of it) will be positioned. If omitted, it
* defaults to 0. * defaults to 0.
*/ */
scrollToEvent: function(eventId, pixelOffset, offsetBase) { scrollToEvent(eventId, pixelOffset, offsetBase) {
if (this.refs.scrollPanel) { if (this.refs.scrollPanel) {
this.refs.scrollPanel.scrollToToken(eventId, pixelOffset, offsetBase); this.refs.scrollPanel.scrollToToken(eventId, pixelOffset, offsetBase);
} }
}, }
scrollToEventIfNeeded: function(eventId) { scrollToEventIfNeeded(eventId) {
const node = this.eventNodes[eventId]; const node = this.eventNodes[eventId];
if (node) { if (node) {
node.scrollIntoView({block: "nearest", behavior: "instant"}); node.scrollIntoView({block: "nearest", behavior: "instant"});
} }
}, }
/* check the scroll state and send out pagination requests if necessary. /* check the scroll state and send out pagination requests if necessary.
*/ */
checkFillState: function() { checkFillState() {
if (this.refs.scrollPanel) { if (this.refs.scrollPanel) {
this.refs.scrollPanel.checkFillState(); this.refs.scrollPanel.checkFillState();
} }
}, }
_isUnmounting: function() { _isUnmounting() {
return !this._isMounted; return !this._isMounted;
}, }
// TODO: Implement granular (per-room) hide options // TODO: Implement granular (per-room) hide options
_shouldShowEvent: function(mxEv) { _shouldShowEvent(mxEv) {
if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) { if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
return false; // ignored = no show (only happens if the ignore happens after an event was received) return false; // ignored = no show (only happens if the ignore happens after an event was received)
} }
@ -320,9 +323,9 @@ module.exports = createReactClass({
if (this.props.highlightedEventId === mxEv.getId()) return true; if (this.props.highlightedEventId === mxEv.getId()) return true;
return !shouldHideEvent(mxEv); return !shouldHideEvent(mxEv);
}, }
_getEventTiles: function() { _getEventTiles() {
const DateSeparator = sdk.getComponent('messages.DateSeparator'); const DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventListSummary = sdk.getComponent('views.elements.EventListSummary'); const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary'); const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
@ -596,9 +599,9 @@ module.exports = createReactClass({
this.currentReadMarkerEventId = readMarkerVisible ? this.props.readMarkerEventId : null; this.currentReadMarkerEventId = readMarkerVisible ? this.props.readMarkerEventId : null;
return ret; return ret;
}, }
_getTilesForEvent: function(prevEvent, mxEv, last) { _getTilesForEvent(prevEvent, mxEv, last) {
const EventTile = sdk.getComponent('rooms.EventTile'); const EventTile = sdk.getComponent('rooms.EventTile');
const DateSeparator = sdk.getComponent('messages.DateSeparator'); const DateSeparator = sdk.getComponent('messages.DateSeparator');
const ret = []; const ret = [];
@ -691,20 +694,20 @@ module.exports = createReactClass({
); );
return ret; return ret;
}, }
_wantsDateSeparator: function(prevEvent, nextEventDate) { _wantsDateSeparator(prevEvent, nextEventDate) {
if (prevEvent == null) { if (prevEvent == null) {
// first event in the panel: depends if we could back-paginate from // first event in the panel: depends if we could back-paginate from
// here. // here.
return !this.props.suppressFirstDateSeparator; return !this.props.suppressFirstDateSeparator;
} }
return wantsDateSeparator(prevEvent.getDate(), nextEventDate); return wantsDateSeparator(prevEvent.getDate(), nextEventDate);
}, }
// Get a list of read receipts that should be shown next to this event // Get a list of read receipts that should be shown next to this event
// Receipts are objects which have a 'userId', 'roomMember' and 'ts'. // Receipts are objects which have a 'userId', 'roomMember' and 'ts'.
_getReadReceiptsForEvent: function(event) { _getReadReceiptsForEvent(event) {
const myUserId = MatrixClientPeg.get().credentials.userId; const myUserId = MatrixClientPeg.get().credentials.userId;
// get list of read receipts, sorted most recent first // get list of read receipts, sorted most recent first
@ -728,12 +731,12 @@ module.exports = createReactClass({
}); });
}); });
return receipts; return receipts;
}, }
// Get an object that maps from event ID to a list of read receipts that // Get an object that maps from event ID to a list of read receipts that
// should be shown next to that event. If a hidden event has read receipts, // should be shown next to that event. If a hidden event has read receipts,
// they are folded into the receipts of the last shown event. // they are folded into the receipts of the last shown event.
_getReadReceiptsByShownEvent: function() { _getReadReceiptsByShownEvent() {
const receiptsByEvent = {}; const receiptsByEvent = {};
const receiptsByUserId = {}; const receiptsByUserId = {};
@ -786,9 +789,9 @@ module.exports = createReactClass({
} }
return receiptsByEvent; return receiptsByEvent;
}, }
_getReadMarkerTile: function(visible) { _getReadMarkerTile(visible) {
let hr; let hr;
if (visible) { if (visible) {
hr = <hr className="mx_RoomView_myReadMarker" hr = <hr className="mx_RoomView_myReadMarker"
@ -802,9 +805,9 @@ module.exports = createReactClass({
{ hr } { hr }
</li> </li>
); );
}, }
_startAnimation: function(ghostNode) { _startAnimation = (ghostNode) => {
if (this._readMarkerGhostNode) { if (this._readMarkerGhostNode) {
Velocity.Utilities.removeData(this._readMarkerGhostNode); Velocity.Utilities.removeData(this._readMarkerGhostNode);
} }
@ -816,9 +819,9 @@ module.exports = createReactClass({
{duration: 400, easing: 'easeInSine', {duration: 400, easing: 'easeInSine',
delay: 1000}); delay: 1000});
} }
}, };
_getReadMarkerGhostTile: function() { _getReadMarkerGhostTile() {
const hr = <hr className="mx_RoomView_myReadMarker" const hr = <hr className="mx_RoomView_myReadMarker"
style={{opacity: 1, width: '99%'}} style={{opacity: 1, width: '99%'}}
ref={this._startAnimation} ref={this._startAnimation}
@ -833,31 +836,31 @@ module.exports = createReactClass({
{ hr } { hr }
</li> </li>
); );
}, }
_collectEventNode: function(eventId, node) { _collectEventNode = (eventId, node) => {
this.eventNodes[eventId] = node; this.eventNodes[eventId] = node;
}, }
// once dynamic content in the events load, make the scrollPanel check the // once dynamic content in the events load, make the scrollPanel check the
// scroll offsets. // scroll offsets.
_onHeightChanged: function() { _onHeightChanged = () => {
const scrollPanel = this.refs.scrollPanel; const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) { if (scrollPanel) {
scrollPanel.checkScroll(); scrollPanel.checkScroll();
} }
}, };
_onTypingShown: function() { _onTypingShown = () => {
const scrollPanel = this.refs.scrollPanel; const scrollPanel = this.refs.scrollPanel;
// this will make the timeline grow, so checkScroll // this will make the timeline grow, so checkScroll
scrollPanel.checkScroll(); scrollPanel.checkScroll();
if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) { if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) {
scrollPanel.preventShrinking(); scrollPanel.preventShrinking();
} }
}, };
_onTypingHidden: function() { _onTypingHidden = () => {
const scrollPanel = this.refs.scrollPanel; const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) { if (scrollPanel) {
// as hiding the typing notifications doesn't // as hiding the typing notifications doesn't
@ -868,9 +871,9 @@ module.exports = createReactClass({
// reveal added padding to balance the notifs disappearing. // reveal added padding to balance the notifs disappearing.
scrollPanel.checkScroll(); scrollPanel.checkScroll();
} }
}, };
updateTimelineMinHeight: function() { updateTimelineMinHeight() {
const scrollPanel = this.refs.scrollPanel; const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) { if (scrollPanel) {
@ -885,16 +888,16 @@ module.exports = createReactClass({
scrollPanel.preventShrinking(); scrollPanel.preventShrinking();
} }
} }
}, }
onTimelineReset: function() { onTimelineReset() {
const scrollPanel = this.refs.scrollPanel; const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) { if (scrollPanel) {
scrollPanel.clearPreventShrinking(); scrollPanel.clearPreventShrinking();
} }
}, }
render: function() { render() {
const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile"); const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
@ -941,5 +944,5 @@ module.exports = createReactClass({
{ bottomSpinner } { bottomSpinner }
</ScrollPanel> </ScrollPanel>
); );
}, }
}); }