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