;
- },
+ }
- render: function() {
+ render() {
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
const Spinner = sdk.getComponent("elements.Spinner");
@@ -1366,5 +1353,5 @@ export default createReactClass({
console.error("Invalid state for GroupView");
return ;
}
- },
-});
+ }
+}
diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js
index fa7860ccef..c8fcd7e9ca 100644
--- a/src/components/structures/InteractiveAuth.js
+++ b/src/components/structures/InteractiveAuth.js
@@ -17,7 +17,6 @@ limitations under the License.
import {InteractiveAuth} from "matrix-js-sdk";
import React, {createRef} from 'react';
-import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents';
@@ -26,10 +25,8 @@ import * as sdk from '../../index';
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
-export default createReactClass({
- displayName: 'InteractiveAuth',
-
- propTypes: {
+export default class InteractiveAuthComponent extends React.Component {
+ static propTypes = {
// matrix client to use for UI auth requests
matrixClient: PropTypes.object.isRequired,
@@ -86,20 +83,19 @@ export default createReactClass({
// continueText and continueKind are passed straight through to the AuthEntryComponent.
continueText: PropTypes.string,
continueKind: PropTypes.string,
- },
+ };
- getInitialState: function() {
- return {
+ constructor(props) {
+ super(props);
+
+ this.state = {
authStage: null,
busy: false,
errorText: null,
stageErrorText: null,
submitButtonEnabled: false,
};
- },
- // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
- UNSAFE_componentWillMount: function() {
this._unmounted = false;
this._authLogic = new InteractiveAuth({
authData: this.props.authData,
@@ -114,6 +110,18 @@ export default createReactClass({
requestEmailToken: this._requestEmailToken,
});
+ this._intervalId = null;
+ if (this.props.poll) {
+ this._intervalId = setInterval(() => {
+ this._authLogic.poll();
+ }, 2000);
+ }
+
+ this._stageComponent = createRef();
+ }
+
+ // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
+ UNSAFE_componentWillMount() { // eslint-disable-line camelcase
this._authLogic.attemptAuth().then((result) => {
const extra = {
emailSid: this._authLogic.getEmailSid(),
@@ -132,26 +140,17 @@ export default createReactClass({
errorText: msg,
});
});
+ }
- this._intervalId = null;
- if (this.props.poll) {
- this._intervalId = setInterval(() => {
- this._authLogic.poll();
- }, 2000);
- }
-
- this._stageComponent = createRef();
- },
-
- componentWillUnmount: function() {
+ componentWillUnmount() {
this._unmounted = true;
if (this._intervalId !== null) {
clearInterval(this._intervalId);
}
- },
+ }
- _requestEmailToken: async function(...args) {
+ _requestEmailToken = async (...args) => {
this.setState({
busy: true,
});
@@ -162,15 +161,15 @@ export default createReactClass({
busy: false,
});
}
- },
+ };
- tryContinue: function() {
+ tryContinue = () => {
if (this._stageComponent.current && this._stageComponent.current.tryContinue) {
this._stageComponent.current.tryContinue();
}
- },
+ };
- _authStateUpdated: function(stageType, stageState) {
+ _authStateUpdated = (stageType, stageState) => {
const oldStage = this.state.authStage;
this.setState({
busy: false,
@@ -180,16 +179,16 @@ export default createReactClass({
}, () => {
if (oldStage != stageType) this._setFocus();
});
- },
+ };
- _requestCallback: function(auth) {
+ _requestCallback = (auth) => {
// This wrapper just exists because the js-sdk passes a second
// 'busy' param for backwards compat. This throws the tests off
// so discard it here.
return this.props.makeRequest(auth);
- },
+ };
- _onBusyChanged: function(busy) {
+ _onBusyChanged = (busy) => {
// if we've started doing stuff, reset the error messages
if (busy) {
this.setState({
@@ -204,29 +203,29 @@ export default createReactClass({
// there's a new screen to show the user. This is implemented by setting
// `busy: false` in `_authStateUpdated`.
// See also https://github.com/vector-im/element-web/issues/12546
- },
+ };
- _setFocus: function() {
+ _setFocus() {
if (this._stageComponent.current && this._stageComponent.current.focus) {
this._stageComponent.current.focus();
}
- },
+ }
- _submitAuthDict: function(authData) {
+ _submitAuthDict = authData => {
this._authLogic.submitAuthDict(authData);
- },
+ };
- _onPhaseChange: function(newPhase) {
+ _onPhaseChange = newPhase => {
if (this.props.onStagePhaseChange) {
this.props.onStagePhaseChange(this.state.authStage, newPhase || 0);
}
- },
+ };
- _onStageCancel: function() {
+ _onStageCancel = () => {
this.props.onAuthFinished(false, ERROR_USER_CANCELLED);
- },
+ };
- _renderCurrentStage: function() {
+ _renderCurrentStage() {
const stage = this.state.authStage;
if (!stage) {
if (this.state.busy) {
@@ -260,16 +259,17 @@ export default createReactClass({
onCancel={this._onStageCancel}
/>
);
- },
+ }
- _onAuthStageFailed: function(e) {
+ _onAuthStageFailed = e => {
this.props.onAuthFinished(false, e);
- },
- _setEmailSid: function(sid) {
- this._authLogic.setEmailSid(sid);
- },
+ };
- render: function() {
+ _setEmailSid = sid => {
+ this._authLogic.setEmailSid(sid);
+ };
+
+ render() {
let error = null;
if (this.state.errorText) {
error = (
@@ -287,5 +287,5 @@ export default createReactClass({
);
- },
-});
+ }
+}
diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js
index 7043c7f38a..e0551eecdb 100644
--- a/src/components/structures/MyGroups.js
+++ b/src/components/structures/MyGroups.js
@@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
-import createReactClass from 'create-react-class';
import * as sdk from '../../index';
import { _t } from '../../languageHandler';
import SdkConfig from '../../SdkConfig';
@@ -26,29 +25,23 @@ import AccessibleButton from '../views/elements/AccessibleButton';
import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar";
-export default createReactClass({
- displayName: 'MyGroups',
+export default class MyGroups extends React.Component {
+ static contextType = MatrixClientContext;
- getInitialState: function() {
- return {
- groups: null,
- error: null,
- };
- },
+ state = {
+ groups: null,
+ error: null,
+ };
- statics: {
- contextType: MatrixClientContext,
- },
-
- componentDidMount: function() {
+ componentDidMount() {
this._fetch();
- },
+ }
- _onCreateGroupClick: function() {
+ _onCreateGroupClick = () => {
dis.dispatch({action: 'view_create_group'});
- },
+ };
- _fetch: function() {
+ _fetch() {
this.context.getJoinedGroups().then((result) => {
this.setState({groups: result.groups, error: null});
}, (err) => {
@@ -59,9 +52,9 @@ export default createReactClass({
}
this.setState({groups: null, error: err});
});
- },
+ }
- render: function() {
+ render() {
const brand = SdkConfig.get().brand;
const Loader = sdk.getComponent("elements.Spinner");
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
@@ -149,5 +142,5 @@ export default createReactClass({
{ content }
;
- },
-});
+ }
+}
diff --git a/src/components/structures/NotificationPanel.js b/src/components/structures/NotificationPanel.js
index c1f78cffda..6ae7f91142 100644
--- a/src/components/structures/NotificationPanel.js
+++ b/src/components/structures/NotificationPanel.js
@@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
-import createReactClass from 'create-react-class';
import { _t } from '../../languageHandler';
import {MatrixClientPeg} from "../../MatrixClientPeg";
import * as sdk from "../../index";
@@ -25,13 +24,8 @@ import * as sdk from "../../index";
/*
* Component which shows the global notification list using a TimelinePanel
*/
-const NotificationPanel = createReactClass({
- displayName: 'NotificationPanel',
-
- propTypes: {
- },
-
- render: function() {
+class NotificationPanel extends React.Component {
+ render() {
// wrap a TimelinePanel with the jump-to-event bits turned off.
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const Loader = sdk.getComponent("elements.Spinner");
@@ -45,7 +39,7 @@ const NotificationPanel = createReactClass({
if (timelineSet) {
return (
-
);
}
- },
-});
+ }
+}
export default NotificationPanel;
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index a4e3254e4c..11416b29fb 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -21,6 +21,8 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
+import {Room} from "matrix-js-sdk/src/models/room";
+
import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher';
import RateLimitedFunc from '../../ratelimitedfunc';
@@ -34,7 +36,7 @@ import {Action} from "../../dispatcher/actions";
export default class RightPanel extends React.Component {
static get propTypes() {
return {
- roomId: PropTypes.string, // if showing panels for a given room, this is set
+ room: PropTypes.instanceOf(Room), // if showing panels for a given room, this is set
groupId: PropTypes.string, // if showing panels for a given group, this is set
user: PropTypes.object, // used if we know the user ahead of opening the panel
};
@@ -42,8 +44,8 @@ export default class RightPanel extends React.Component {
static contextType = MatrixClientContext;
- constructor(props) {
- super(props);
+ constructor(props, context) {
+ super(props, context);
this.state = {
phase: this._getPhaseFromProps(),
isUserPrivilegedInGroup: null,
@@ -161,13 +163,13 @@ export default class RightPanel extends React.Component {
}
onRoomStateMember(ev, state, member) {
- if (member.roomId !== this.props.roomId) {
+ if (member.roomId !== this.props.room.roomId) {
return;
}
// redraw the badge on the membership list
- if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.roomId) {
+ if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.room.roomId) {
this._delayedUpdate();
- } else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.roomId &&
+ } else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.room.roomId &&
member.userId === this.state.member.userId) {
// refresh the member info (e.g. new power level)
this._delayedUpdate();
@@ -226,8 +228,8 @@ export default class RightPanel extends React.Component {
switch (this.state.phase) {
case RightPanelPhases.RoomMemberList:
- if (this.props.roomId) {
- panel = ;
+ if (this.props.room.roomId) {
+ panel = ;
}
break;
case RightPanelPhases.GroupMemberList:
@@ -242,8 +244,8 @@ export default class RightPanel extends React.Component {
case RightPanelPhases.EncryptionPanel:
panel = ;
break;
case RightPanelPhases.Room3pidMemberInfo:
- panel = ;
+ panel = ;
break;
case RightPanelPhases.GroupMemberInfo:
panel = ;
break;
case RightPanelPhases.FilePanel:
- panel = ;
+ panel = ;
break;
}
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index 11d3508ee5..16ab8edbed 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
-import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../MatrixClientPeg";
import * as sdk from "../../index";
import dis from "../../dispatcher/dispatcher";
@@ -42,16 +41,16 @@ function track(action) {
Analytics.trackEvent('RoomDirectory', action);
}
-export default createReactClass({
- displayName: 'RoomDirectory',
-
- propTypes: {
+export default class RoomDirectory extends React.Component {
+ static propTypes = {
onFinished: PropTypes.func.isRequired,
- },
+ };
+
+ constructor(props) {
+ super(props);
- getInitialState: function() {
const selectedCommunityId = TagOrderStore.getSelectedTags()[0];
- return {
+ this.state = {
publicRooms: [],
loading: true,
protocolsLoading: true,
@@ -64,10 +63,7 @@ export default createReactClass({
: null,
communityName: null,
};
- },
- // TODO: [REACT-WARNING] Move this to constructor
- UNSAFE_componentWillMount: function() {
this._unmounted = false;
this.nextBatch = null;
this.filterTimeout = null;
@@ -115,16 +111,16 @@ export default createReactClass({
}
this.refreshRoomList();
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
if (this.filterTimeout) {
clearTimeout(this.filterTimeout);
}
this._unmounted = true;
- },
+ }
- refreshRoomList: function() {
+ refreshRoomList = () => {
if (this.state.selectedCommunityId) {
this.setState({
publicRooms: GroupStore.getGroupRooms(this.state.selectedCommunityId).map(r => {
@@ -158,9 +154,9 @@ export default createReactClass({
loading: true,
});
this.getMoreRooms();
- },
+ };
- getMoreRooms: function() {
+ getMoreRooms() {
if (this.state.selectedCommunityId) return Promise.resolve(); // no more rooms
if (!MatrixClientPeg.get()) return Promise.resolve();
@@ -233,7 +229,7 @@ export default createReactClass({
),
});
});
- },
+ }
/**
* A limited interface for removing rooms from the directory.
@@ -242,7 +238,7 @@ export default createReactClass({
* HS admins to do this through the RoomSettings interface, but
* this needs SPEC-417.
*/
- removeFromDirectory: function(room) {
+ removeFromDirectory(room) {
const alias = get_display_alias_for_room(room);
const name = room.name || alias || _t('Unnamed room');
@@ -284,18 +280,18 @@ export default createReactClass({
});
},
});
- },
+ }
- onRoomClicked: function(room, ev) {
+ onRoomClicked = (room, ev) => {
if (ev.shiftKey && !this.state.selectedCommunityId) {
ev.preventDefault();
this.removeFromDirectory(room);
} else {
this.showRoom(room);
}
- },
+ };
- onOptionChange: function(server, instanceId) {
+ onOptionChange = (server, instanceId) => {
// clear next batch so we don't try to load more rooms
this.nextBatch = null;
this.setState({
@@ -313,15 +309,15 @@ export default createReactClass({
// find the five gitter ones, at which point we do not want
// to render all those rooms when switching back to 'all networks'.
// Easiest to just blow away the state & re-fetch.
- },
+ };
- onFillRequest: function(backwards) {
+ onFillRequest = (backwards) => {
if (backwards || !this.nextBatch) return Promise.resolve(false);
return this.getMoreRooms();
- },
+ };
- onFilterChange: function(alias) {
+ onFilterChange = (alias) => {
this.setState({
filterString: alias || null,
});
@@ -337,9 +333,9 @@ export default createReactClass({
this.filterTimeout = null;
this.refreshRoomList();
}, 700);
- },
+ };
- onFilterClear: function() {
+ onFilterClear = () => {
// update immediately
this.setState({
filterString: null,
@@ -348,9 +344,9 @@ export default createReactClass({
if (this.filterTimeout) {
clearTimeout(this.filterTimeout);
}
- },
+ };
- onJoinFromSearchClick: function(alias) {
+ onJoinFromSearchClick = (alias) => {
// If we don't have a particular instance id selected, just show that rooms alias
if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) {
// If the user specified an alias without a domain, add on whichever server is selected
@@ -391,9 +387,9 @@ export default createReactClass({
});
});
}
- },
+ };
- onPreviewClick: function(ev, room) {
+ onPreviewClick = (ev, room) => {
this.props.onFinished();
dis.dispatch({
action: 'view_room',
@@ -401,9 +397,9 @@ export default createReactClass({
should_peek: true,
});
ev.stopPropagation();
- },
+ };
- onViewClick: function(ev, room) {
+ onViewClick = (ev, room) => {
this.props.onFinished();
dis.dispatch({
action: 'view_room',
@@ -411,26 +407,26 @@ export default createReactClass({
should_peek: false,
});
ev.stopPropagation();
- },
+ };
- onJoinClick: function(ev, room) {
+ onJoinClick = (ev, room) => {
this.showRoom(room, null, true);
ev.stopPropagation();
- },
+ };
- onCreateRoomClick: function(room) {
+ onCreateRoomClick = room => {
this.props.onFinished();
dis.dispatch({
action: 'view_create_room',
public: true,
});
- },
+ };
- showRoomAlias: function(alias, autoJoin=false) {
+ showRoomAlias(alias, autoJoin=false) {
this.showRoom(null, alias, autoJoin);
- },
+ }
- showRoom: function(room, room_alias, autoJoin=false) {
+ showRoom(room, room_alias, autoJoin=false) {
this.props.onFinished();
const payload = {
action: 'view_room',
@@ -474,7 +470,7 @@ export default createReactClass({
payload.room_id = room.room_id;
}
dis.dispatch(payload);
- },
+ }
getRow(room) {
const client = MatrixClientPeg.get();
@@ -540,22 +536,22 @@ export default createReactClass({
{joinOrViewButton}
);
- },
+ }
- collectScrollPanel: function(element) {
+ collectScrollPanel = (element) => {
this.scrollPanel = element;
- },
+ };
- _stringLooksLikeId: function(s, field_type) {
+ _stringLooksLikeId(s, field_type) {
let pat = /^#[^\s]+:[^\s]/;
if (field_type && field_type.regexp) {
pat = new RegExp(field_type.regexp);
}
return pat.test(s);
- },
+ }
- _getFieldsForThirdPartyLocation: function(userInput, protocol, instance) {
+ _getFieldsForThirdPartyLocation(userInput, protocol, instance) {
// make an object with the fields specified by that protocol. We
// require that the values of all but the last field come from the
// instance. The last is the user input.
@@ -569,20 +565,20 @@ export default createReactClass({
}
fields[requiredFields[requiredFields.length - 1]] = userInput;
return fields;
- },
+ }
/**
* called by the parent component when PageUp/Down/etc is pressed.
*
* We pass it down to the scroll panel.
*/
- handleScrollKey: function(ev) {
+ handleScrollKey = ev => {
if (this.scrollPanel) {
this.scrollPanel.handleScrollKey(ev);
}
- },
+ };
- render: function() {
+ render() {
const Loader = sdk.getComponent("elements.Spinner");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@@ -712,8 +708,8 @@ export default createReactClass({
);
- },
-});
+ }
+}
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
// but works with the objects we get from the public room list
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index 3171dbccbe..cdaa0bb7f9 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
-import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import Matrix from 'matrix-js-sdk';
import { _t, _td } from '../../languageHandler';
@@ -39,10 +38,8 @@ function getUnsentMessages(room) {
});
}
-export default createReactClass({
- displayName: 'RoomStatusBar',
-
- propTypes: {
+export default class RoomStatusBar extends React.Component {
+ static propTypes = {
// the room this statusbar is representing.
room: PropTypes.object.isRequired,
// This is true when the user is alone in the room, but has also sent a message.
@@ -86,37 +83,35 @@ export default createReactClass({
// callback for when the status bar is displaying something and should
// be visible
onVisible: PropTypes.func,
- },
+ };
- getInitialState: function() {
- return {
- syncState: MatrixClientPeg.get().getSyncState(),
- syncStateData: MatrixClientPeg.get().getSyncStateData(),
- unsentMessages: getUnsentMessages(this.props.room),
- };
- },
+ state = {
+ syncState: MatrixClientPeg.get().getSyncState(),
+ syncStateData: MatrixClientPeg.get().getSyncStateData(),
+ unsentMessages: getUnsentMessages(this.props.room),
+ };
- componentDidMount: function() {
+ componentDidMount() {
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
this._checkSize();
- },
+ }
- componentDidUpdate: function() {
+ componentDidUpdate() {
this._checkSize();
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
// we may have entirely lost our client as we're logging out before clicking login on the guest bar...
const client = MatrixClientPeg.get();
if (client) {
client.removeListener("sync", this.onSyncStateChange);
client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
}
- },
+ }
- onSyncStateChange: function(state, prevState, data) {
+ onSyncStateChange = (state, prevState, data) => {
if (state === "SYNCING" && prevState === "SYNCING") {
return;
}
@@ -124,39 +119,39 @@ export default createReactClass({
syncState: state,
syncStateData: data,
});
- },
+ };
- _onResendAllClick: function() {
+ _onResendAllClick = () => {
Resend.resendUnsentEvents(this.props.room);
dis.fire(Action.FocusComposer);
- },
+ };
- _onCancelAllClick: function() {
+ _onCancelAllClick = () => {
Resend.cancelUnsentEvents(this.props.room);
dis.fire(Action.FocusComposer);
- },
+ };
- _onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) {
+ _onRoomLocalEchoUpdated = (event, room, oldEventId, oldStatus) => {
if (room.roomId !== this.props.room.roomId) return;
this.setState({
unsentMessages: getUnsentMessages(this.props.room),
});
- },
+ };
// Check whether current size is greater than 0, if yes call props.onVisible
- _checkSize: function() {
+ _checkSize() {
if (this._getSize()) {
if (this.props.onVisible) this.props.onVisible();
} else {
if (this.props.onHidden) this.props.onHidden();
}
- },
+ }
// We don't need the actual height - just whether it is likely to have
// changed - so we use '0' to indicate normal size, and other values to
// indicate other sizes.
- _getSize: function() {
+ _getSize() {
if (this._shouldShowConnectionError() ||
this.props.hasActiveCall ||
this.props.sentMessageAndIsAlone
@@ -166,10 +161,10 @@ export default createReactClass({
return STATUS_BAR_EXPANDED_LARGE;
}
return STATUS_BAR_HIDDEN;
- },
+ }
// return suitable content for the image on the left of the status bar.
- _getIndicator: function() {
+ _getIndicator() {
if (this.props.hasActiveCall) {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
return (
@@ -182,9 +177,9 @@ export default createReactClass({
}
return null;
- },
+ }
- _shouldShowConnectionError: function() {
+ _shouldShowConnectionError() {
// no conn bar trumps the "some not sent" msg since you can't resend without
// a connection!
// There's one situation in which we don't show this 'no connection' bar, and that's
@@ -195,9 +190,9 @@ export default createReactClass({
this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED',
);
return this.state.syncState === "ERROR" && !errorIsMauError;
- },
+ }
- _getUnsentMessageContent: function() {
+ _getUnsentMessageContent() {
const unsentMessages = this.state.unsentMessages;
if (!unsentMessages.length) return null;
@@ -272,10 +267,10 @@ export default createReactClass({
;
- },
+ }
// return suitable content for the main (text) part of the status bar.
- _getContent: function() {
+ _getContent() {
if (this._shouldShowConnectionError()) {
return (
);
- },
-});
+ }
+}
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 2e06be7b4c..d98a19ebe8 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -24,7 +24,6 @@ limitations under the License.
import shouldHideEvent from '../../shouldHideEvent';
import React, {createRef} from 'react';
-import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { _t } from '../../languageHandler';
@@ -68,9 +67,8 @@ if (DEBUG) {
debuglog = console.log.bind(console);
}
-export default createReactClass({
- displayName: 'RoomView',
- propTypes: {
+export default class RoomView extends React.Component {
+ static propTypes = {
ConferenceHandler: PropTypes.any,
// Called with the credentials of a registered user (if they were a ROU that
@@ -97,15 +95,15 @@ export default createReactClass({
// Servers the RoomView can use to try and assist joins
viaServers: PropTypes.arrayOf(PropTypes.string),
- },
+ };
- statics: {
- contextType: MatrixClientContext,
- },
+ static contextType = MatrixClientContext;
+
+ constructor(props, context) {
+ super(props, context);
- getInitialState: function() {
const llMembers = this.context.hasLazyLoadMembersEnabled();
- return {
+ this.state = {
room: null,
roomId: null,
roomLoading: true,
@@ -171,10 +169,7 @@ export default createReactClass({
matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
};
- },
- // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
- UNSAFE_componentWillMount: function() {
this.dispatcherRef = dis.register(this.onAction);
this.context.on("Room", this.onRoom);
this.context.on("Room.timeline", this.onRoomTimeline);
@@ -191,7 +186,6 @@ export default createReactClass({
// Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate);
- this._onRoomViewStoreUpdate(true);
WidgetEchoStore.on('update', this._onWidgetEchoStoreUpdate);
this._showReadReceiptsWatchRef = SettingsStore.watchSetting("showReadReceipts", null,
@@ -201,15 +195,20 @@ export default createReactClass({
this._searchResultsPanel = createRef();
this._layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange);
- },
+ }
- _onReadReceiptsChange: function() {
+ // TODO: [REACT-WARNING] Move into constructor
+ UNSAFE_componentWillMount() {
+ this._onRoomViewStoreUpdate(true);
+ }
+
+ _onReadReceiptsChange = () => {
this.setState({
showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId),
});
- },
+ };
- _onRoomViewStoreUpdate: function(initial) {
+ _onRoomViewStoreUpdate = initial => {
if (this.unmounted) {
return;
}
@@ -303,7 +302,7 @@ export default createReactClass({
if (initial) {
this._setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek);
}
- },
+ };
_getRoomId() {
// According to `_onRoomViewStoreUpdate`, `state.roomId` can be null
@@ -312,9 +311,9 @@ export default createReactClass({
// the bare room ID. (We may want to update `state.roomId` after
// resolving aliases, so we could always trust it.)
return this.state.room ? this.state.room.roomId : this.state.roomId;
- },
+ }
- _getPermalinkCreatorForRoom: function(room) {
+ _getPermalinkCreatorForRoom(room) {
if (!this._permalinkCreators) this._permalinkCreators = {};
if (this._permalinkCreators[room.roomId]) return this._permalinkCreators[room.roomId];
@@ -327,22 +326,22 @@ export default createReactClass({
this._permalinkCreators[room.roomId].load();
}
return this._permalinkCreators[room.roomId];
- },
+ }
- _stopAllPermalinkCreators: function() {
+ _stopAllPermalinkCreators() {
if (!this._permalinkCreators) return;
for (const roomId of Object.keys(this._permalinkCreators)) {
this._permalinkCreators[roomId].stop();
}
- },
+ }
- _onWidgetEchoStoreUpdate: function() {
+ _onWidgetEchoStoreUpdate = () => {
this.setState({
showApps: this._shouldShowApps(this.state.room),
});
- },
+ };
- _setupRoom: function(room, roomId, joining, shouldPeek) {
+ _setupRoom(room, roomId, joining, shouldPeek) {
// if this is an unknown room then we're in one of three states:
// - This is a room we can peek into (search engine) (we can /peek)
// - This is a room we can publicly join or were invited to. (we can /join)
@@ -404,9 +403,9 @@ export default createReactClass({
this.setState({isPeeking: false});
}
}
- },
+ }
- _shouldShowApps: function(room) {
+ _shouldShowApps(room) {
if (!BROWSER_SUPPORTS_SANDBOX) return false;
// Check if user has previously chosen to hide the app drawer for this
@@ -417,9 +416,9 @@ export default createReactClass({
// This is confusing, but it means to say that we default to the tray being
// hidden unless the user clicked to open it.
return hideWidgetDrawer === "false";
- },
+ }
- componentDidMount: function() {
+ componentDidMount() {
const call = this._getCallForRoom();
const callState = call ? call.call_state : "ended";
this.setState({
@@ -435,14 +434,14 @@ export default createReactClass({
this.onResize();
document.addEventListener("keydown", this.onNativeKeyDown);
- },
+ }
- shouldComponentUpdate: function(nextProps, nextState) {
+ shouldComponentUpdate(nextProps, nextState) {
return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
!ObjectUtils.shallowEqual(this.state, nextState));
- },
+ }
- componentDidUpdate: function() {
+ componentDidUpdate() {
if (this._roomView.current) {
const roomView = this._roomView.current;
if (!roomView.ondrop) {
@@ -464,9 +463,9 @@ export default createReactClass({
atEndOfLiveTimeline: this._messagePanel.isAtEndOfLiveTimeline(),
});
}
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
// set a boolean to say we've been unmounted, which any pending
// promises can use to throw away their results.
//
@@ -543,21 +542,21 @@ export default createReactClass({
// Tinter.tint(); // reset colourscheme
SettingsStore.unwatchSetting(this._layoutWatcherRef);
- },
+ }
- onLayoutChange: function() {
+ onLayoutChange = () => {
this.setState({
useIRCLayout: SettingsStore.getValue("useIRCLayout"),
});
- },
+ };
- _onRightPanelStoreUpdate: function() {
+ _onRightPanelStoreUpdate = () => {
this.setState({
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
});
- },
+ };
- onPageUnload(event) {
+ onPageUnload = event => {
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
return event.returnValue =
_t("You seem to be uploading files, are you sure you want to quit?");
@@ -565,10 +564,10 @@ export default createReactClass({
return event.returnValue =
_t("You seem to be in a call, are you sure you want to quit?");
}
- },
+ };
// we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire
- onNativeKeyDown: function(ev) {
+ onNativeKeyDown = ev => {
let handled = false;
const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev);
@@ -592,9 +591,9 @@ export default createReactClass({
ev.stopPropagation();
ev.preventDefault();
}
- },
+ };
- onReactKeyDown: function(ev) {
+ onReactKeyDown = ev => {
let handled = false;
switch (ev.key) {
@@ -613,7 +612,7 @@ export default createReactClass({
break;
case Key.U.toUpperCase():
if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) && ev.shiftKey) {
- dis.dispatch({ action: "upload_file" })
+ dis.dispatch({ action: "upload_file" });
handled = true;
}
break;
@@ -623,9 +622,9 @@ export default createReactClass({
ev.stopPropagation();
ev.preventDefault();
}
- },
+ };
- onAction: function(payload) {
+ onAction = payload => {
switch (payload.action) {
case 'message_send_failed':
case 'message_sent':
@@ -709,9 +708,9 @@ export default createReactClass({
}
break;
}
- },
+ };
- onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) {
+ onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => {
if (this.unmounted) return;
// ignore events for other rooms
@@ -747,51 +746,51 @@ export default createReactClass({
});
}
}
- },
+ };
- onRoomName: function(room) {
+ onRoomName = room => {
if (this.state.room && room.roomId == this.state.room.roomId) {
this.forceUpdate();
}
- },
+ };
- onRoomRecoveryReminderDontAskAgain: function() {
+ onRoomRecoveryReminderDontAskAgain = () => {
// Called when the option to not ask again is set:
// force an update to hide the recovery reminder
this.forceUpdate();
- },
+ };
- onKeyBackupStatus() {
+ onKeyBackupStatus = () => {
// Key backup status changes affect whether the in-room recovery
// reminder is displayed.
this.forceUpdate();
- },
+ };
- canResetTimeline: function() {
+ canResetTimeline = () => {
if (!this._messagePanel) {
return true;
}
return this._messagePanel.canResetTimeline();
- },
+ };
// called when state.room is first initialised (either at initial load,
// after a successful peek, or after we join the room).
- _onRoomLoaded: function(room) {
+ _onRoomLoaded = room => {
this._calculatePeekRules(room);
this._updatePreviewUrlVisibility(room);
this._loadMembersIfJoined(room);
this._calculateRecommendedVersion(room);
this._updateE2EStatus(room);
this._updatePermissions(room);
- },
+ };
- _calculateRecommendedVersion: async function(room) {
+ async _calculateRecommendedVersion(room) {
this.setState({
upgradeRecommendation: await room.getRecommendedVersion(),
});
- },
+ }
- _loadMembersIfJoined: async function(room) {
+ async _loadMembersIfJoined(room) {
// lazy load members if enabled
if (this.context.hasLazyLoadMembersEnabled()) {
if (room && room.getMyMembership() === 'join') {
@@ -808,9 +807,9 @@ export default createReactClass({
}
}
}
- },
+ }
- _calculatePeekRules: function(room) {
+ _calculatePeekRules(room) {
const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", "");
if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") {
this.setState({
@@ -824,17 +823,17 @@ export default createReactClass({
canPeek: true,
});
}
- },
+ }
- _updatePreviewUrlVisibility: function({roomId}) {
+ _updatePreviewUrlVisibility({roomId}) {
// URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit
const key = this.context.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled';
this.setState({
showUrlPreview: SettingsStore.getValue(key, roomId),
});
- },
+ }
- onRoom: function(room) {
+ onRoom = room => {
if (!room || room.roomId !== this.state.roomId) {
return;
}
@@ -843,32 +842,32 @@ export default createReactClass({
}, () => {
this._onRoomLoaded(room);
});
- },
+ };
- onDeviceVerificationChanged: function(userId, device) {
+ onDeviceVerificationChanged = (userId, device) => {
const room = this.state.room;
if (!room.currentState.getMember(userId)) {
return;
}
this._updateE2EStatus(room);
- },
+ };
- onUserVerificationChanged: function(userId, _trustStatus) {
+ onUserVerificationChanged = (userId, _trustStatus) => {
const room = this.state.room;
if (!room || !room.currentState.getMember(userId)) {
return;
}
this._updateE2EStatus(room);
- },
+ };
- onCrossSigningKeysChanged: function() {
+ onCrossSigningKeysChanged = () => {
const room = this.state.room;
if (room) {
this._updateE2EStatus(room);
}
- },
+ };
- _updateE2EStatus: async function(room) {
+ async _updateE2EStatus(room) {
if (!this.context.isRoomEncrypted(room.roomId)) {
return;
}
@@ -886,26 +885,26 @@ export default createReactClass({
this.setState({
e2eStatus: await shieldStatusForRoom(this.context, room),
});
- },
+ }
- updateTint: function() {
+ updateTint() {
const room = this.state.room;
if (!room) return;
console.log("Tinter.tint from updateTint");
const colorScheme = SettingsStore.getValue("roomColor", room.roomId);
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
- },
+ }
- onAccountData: function(event) {
+ onAccountData = event => {
const type = event.getType();
if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) {
// non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls`
this._updatePreviewUrlVisibility(this.state.room);
}
- },
+ };
- onRoomAccountData: function(event, room) {
+ onRoomAccountData = (event, room) => {
if (room.roomId == this.state.roomId) {
const type = event.getType();
if (type === "org.matrix.room.color_scheme") {
@@ -918,18 +917,18 @@ export default createReactClass({
this._updatePreviewUrlVisibility(room);
}
}
- },
+ };
- onRoomStateEvents: function(ev, state) {
+ onRoomStateEvents = (ev, state) => {
// ignore if we don't have a room yet
if (!this.state.room || this.state.room.roomId !== state.roomId) {
return;
}
this._updatePermissions(this.state.room);
- },
+ };
- onRoomStateMember: function(ev, state, member) {
+ onRoomStateMember = (ev, state, member) => {
// ignore if we don't have a room yet
if (!this.state.room) {
return;
@@ -941,17 +940,17 @@ export default createReactClass({
}
this._updateRoomMembers(member);
- },
+ };
- onMyMembership: function(room, membership, oldMembership) {
+ onMyMembership = (room, membership, oldMembership) => {
if (room.roomId === this.state.roomId) {
this.forceUpdate();
this._loadMembersIfJoined(room);
this._updatePermissions(room);
}
- },
+ };
- _updatePermissions: function(room) {
+ _updatePermissions(room) {
if (room) {
const me = this.context.getUserId();
const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me);
@@ -959,11 +958,11 @@ export default createReactClass({
this.setState({canReact, canReply});
}
- },
+ }
// rate limited because a power level change will emit an event for every
// member in the room.
- _updateRoomMembers: rate_limited_func(function(dueToMember) {
+ _updateRoomMembers = rate_limited_func((dueToMember) => {
// a member state changed in this room
// refresh the conf call notification state
this._updateConfCallNotification();
@@ -978,9 +977,9 @@ export default createReactClass({
this._checkIfAlone(this.state.room, memberCountInfluence);
this._updateE2EStatus(this.state.room);
- }, 500),
+ }, 500);
- _checkIfAlone: function(room, countInfluence) {
+ _checkIfAlone(room, countInfluence) {
let warnedAboutLonelyRoom = false;
if (localStorage) {
warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId);
@@ -993,9 +992,9 @@ export default createReactClass({
let joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount();
if (countInfluence) joinedOrInvitedMemberCount += countInfluence;
this.setState({isAlone: joinedOrInvitedMemberCount === 1});
- },
+ }
- _updateConfCallNotification: function() {
+ _updateConfCallNotification() {
const room = this.state.room;
if (!room || !this.props.ConferenceHandler) {
return;
@@ -1017,7 +1016,7 @@ export default createReactClass({
confMember.membership === "join"
),
});
- },
+ }
_updateDMState() {
const room = this.state.room;
@@ -1028,9 +1027,9 @@ export default createReactClass({
if (dmInviter) {
Rooms.setDMRoom(room.roomId, dmInviter);
}
- },
+ }
- onSearchResultsFillRequest: function(backwards) {
+ onSearchResultsFillRequest = backwards => {
if (!backwards) {
return Promise.resolve(false);
}
@@ -1043,25 +1042,25 @@ export default createReactClass({
debuglog("no more search results");
return Promise.resolve(false);
}
- },
+ };
- onInviteButtonClick: function() {
+ onInviteButtonClick = () => {
// call AddressPickerDialog
dis.dispatch({
action: 'view_invite',
roomId: this.state.room.roomId,
});
this.setState({isAlone: false}); // there's a good chance they'll invite someone
- },
+ };
- onStopAloneWarningClick: function() {
+ onStopAloneWarningClick = () => {
if (localStorage) {
localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, true);
}
this.setState({isAlone: false});
- },
+ };
- onJoinButtonClicked: function(ev) {
+ onJoinButtonClicked = ev => {
// If the user is a ROU, allow them to transition to a PWLU
if (this.context && this.context.isGuest()) {
// Join this room once the user has registered and logged in
@@ -1120,10 +1119,9 @@ export default createReactClass({
return Promise.resolve();
});
}
+ };
- },
-
- onMessageListScroll: function(ev) {
+ onMessageListScroll = ev => {
if (this._messagePanel.isAtEndOfLiveTimeline()) {
this.setState({
numUnreadMessages: 0,
@@ -1135,9 +1133,9 @@ export default createReactClass({
});
}
this._updateTopUnreadMessagesBar();
- },
+ };
- onDragOver: function(ev) {
+ onDragOver = ev => {
ev.stopPropagation();
ev.preventDefault();
@@ -1154,9 +1152,9 @@ export default createReactClass({
ev.dataTransfer.dropEffect = 'copy';
}
}
- },
+ };
- onDrop: function(ev) {
+ onDrop = ev => {
ev.stopPropagation();
ev.preventDefault();
ContentMessages.sharedInstance().sendContentListToRoom(
@@ -1164,15 +1162,15 @@ export default createReactClass({
);
this.setState({ draggingFile: false });
dis.fire(Action.FocusComposer);
- },
+ };
- onDragLeaveOrEnd: function(ev) {
+ onDragLeaveOrEnd = ev => {
ev.stopPropagation();
ev.preventDefault();
this.setState({ draggingFile: false });
- },
+ };
- injectSticker: function(url, info, text) {
+ injectSticker(url, info, text) {
if (this.context.isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
@@ -1185,9 +1183,9 @@ export default createReactClass({
return;
}
});
- },
+ }
- onSearch: function(term, scope) {
+ onSearch = (term, scope) => {
this.setState({
searchTerm: term,
searchScope: scope,
@@ -1213,9 +1211,9 @@ export default createReactClass({
debuglog("sending search request");
const searchPromise = eventSearch(term, roomId);
this._handleSearchResult(searchPromise);
- },
+ };
- _handleSearchResult: function(searchPromise) {
+ _handleSearchResult(searchPromise) {
const self = this;
// keep a record of the current search id, so that if the search terms
@@ -1266,9 +1264,9 @@ export default createReactClass({
searchInProgress: false,
});
});
- },
+ }
- getSearchResultTiles: function() {
+ getSearchResultTiles() {
const SearchResultTile = sdk.getComponent('rooms.SearchResultTile');
const Spinner = sdk.getComponent("elements.Spinner");
@@ -1348,20 +1346,20 @@ export default createReactClass({
onHeightChanged={onHeightChanged} />);
}
return ret;
- },
+ }
- onPinnedClick: function() {
+ onPinnedClick = () => {
const nowShowingPinned = !this.state.showingPinned;
const roomId = this.state.room.roomId;
this.setState({showingPinned: nowShowingPinned, searching: false});
SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned);
- },
+ };
- onSettingsClick: function() {
+ onSettingsClick = () => {
dis.dispatch({ action: 'open_room_settings' });
- },
+ };
- onCancelClick: function() {
+ onCancelClick = () => {
console.log("updateTint from onCancelClick");
this.updateTint();
if (this.state.forwardingEvent) {
@@ -1371,23 +1369,23 @@ export default createReactClass({
});
}
dis.fire(Action.FocusComposer);
- },
+ };
- onLeaveClick: function() {
+ onLeaveClick = () => {
dis.dispatch({
action: 'leave_room',
room_id: this.state.room.roomId,
});
- },
+ };
- onForgetClick: function() {
+ onForgetClick = () => {
dis.dispatch({
action: 'forget_room',
room_id: this.state.room.roomId,
});
- },
+ };
- onRejectButtonClicked: function(ev) {
+ onRejectButtonClicked = ev => {
const self = this;
this.setState({
rejecting: true,
@@ -1412,9 +1410,9 @@ export default createReactClass({
rejectError: error,
});
});
- },
+ };
- onRejectAndIgnoreClick: async function() {
+ onRejectAndIgnoreClick = async () => {
this.setState({
rejecting: true,
});
@@ -1446,49 +1444,49 @@ export default createReactClass({
rejectError: error,
});
}
- },
+ };
- onRejectThreepidInviteButtonClicked: function(ev) {
+ onRejectThreepidInviteButtonClicked = ev => {
// We can reject 3pid invites in the same way that we accept them,
// using /leave rather than /join. In the short term though, we
// just ignore them.
// https://github.com/vector-im/vector-web/issues/1134
dis.fire(Action.ViewRoomDirectory);
- },
+ };
- onSearchClick: function() {
+ onSearchClick = () => {
this.setState({
searching: !this.state.searching,
showingPinned: false,
});
- },
+ };
- onCancelSearchClick: function() {
+ onCancelSearchClick = () => {
this.setState({
searching: false,
searchResults: null,
});
- },
+ };
// jump down to the bottom of this room, where new events are arriving
- jumpToLiveTimeline: function() {
+ jumpToLiveTimeline = () => {
this._messagePanel.jumpToLiveTimeline();
dis.fire(Action.FocusComposer);
- },
+ };
// jump up to wherever our read marker is
- jumpToReadMarker: function() {
+ jumpToReadMarker = () => {
this._messagePanel.jumpToReadMarker();
- },
+ };
// update the read marker to match the read-receipt
- forgetReadMarker: function(ev) {
+ forgetReadMarker = ev => {
ev.stopPropagation();
this._messagePanel.forgetReadMarker();
- },
+ };
// decide whether or not the top 'unread messages' bar should be shown
- _updateTopUnreadMessagesBar: function() {
+ _updateTopUnreadMessagesBar = () => {
if (!this._messagePanel) {
return;
}
@@ -1497,12 +1495,12 @@ export default createReactClass({
if (this.state.showTopUnreadMessagesBar != showBar) {
this.setState({showTopUnreadMessagesBar: showBar});
}
- },
+ };
// get the current scroll position of the room, so that it can be
// restored when we switch back to it.
//
- _getScrollState: function() {
+ _getScrollState() {
const messagePanel = this._messagePanel;
if (!messagePanel) return null;
@@ -1537,9 +1535,9 @@ export default createReactClass({
focussedEvent: scrollState.trackedScrollToken,
pixelOffset: scrollState.pixelOffset,
};
- },
+ }
- onResize: function() {
+ onResize = () => {
// It seems flexbox doesn't give us a way to constrain the auxPanel height to have
// a minimum of the height of the video element, whilst also capping it from pushing out the page
// so we have to do it via JS instead. In this implementation we cap the height by putting
@@ -1557,16 +1555,16 @@ export default createReactClass({
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
this.setState({auxPanelMaxHeight: auxPanelMaxHeight});
- },
+ };
- onFullscreenClick: function() {
+ onFullscreenClick = () => {
dis.dispatch({
action: 'video_fullscreen',
fullscreen: true,
}, true);
- },
+ };
- onMuteAudioClick: function() {
+ onMuteAudioClick = () => {
const call = this._getCallForRoom();
if (!call) {
return;
@@ -1574,9 +1572,9 @@ export default createReactClass({
const newState = !call.isMicrophoneMuted();
call.setMicrophoneMuted(newState);
this.forceUpdate(); // TODO: just update the voip buttons
- },
+ };
- onMuteVideoClick: function() {
+ onMuteVideoClick = () => {
const call = this._getCallForRoom();
if (!call) {
return;
@@ -1584,29 +1582,29 @@ export default createReactClass({
const newState = !call.isLocalVideoMuted();
call.setLocalVideoMuted(newState);
this.forceUpdate(); // TODO: just update the voip buttons
- },
+ };
- onStatusBarVisible: function() {
+ onStatusBarVisible = () => {
if (this.unmounted) return;
this.setState({
statusBarVisible: true,
});
- },
+ };
- onStatusBarHidden: function() {
+ onStatusBarHidden = () => {
// This is currently not desired as it is annoying if it keeps expanding and collapsing
if (this.unmounted) return;
this.setState({
statusBarVisible: false,
});
- },
+ };
/**
* called by the parent component when PageUp/Down/etc is pressed.
*
* We pass it down to the scroll panel.
*/
- handleScrollKey: function(ev) {
+ handleScrollKey = ev => {
let panel;
if (this._searchResultsPanel.current) {
panel = this._searchResultsPanel.current;
@@ -1617,48 +1615,48 @@ export default createReactClass({
if (panel) {
panel.handleScrollKey(ev);
}
- },
+ };
/**
* get any current call for this room
*/
- _getCallForRoom: function() {
+ _getCallForRoom() {
if (!this.state.room) {
return null;
}
return CallHandler.getCallForRoom(this.state.room.roomId);
- },
+ }
// this has to be a proper method rather than an unnamed function,
// otherwise react calls it with null on each update.
- _gatherTimelinePanelRef: function(r) {
+ _gatherTimelinePanelRef = r => {
this._messagePanel = r;
if (r) {
console.log("updateTint from RoomView._gatherTimelinePanelRef");
this.updateTint();
}
- },
+ };
- _getOldRoom: function() {
+ _getOldRoom() {
const createEvent = this.state.room.currentState.getStateEvents("m.room.create", "");
if (!createEvent || !createEvent.getContent()['predecessor']) return null;
return this.context.getRoom(createEvent.getContent()['predecessor']['room_id']);
- },
+ }
- _getHiddenHighlightCount: function() {
+ _getHiddenHighlightCount() {
const oldRoom = this._getOldRoom();
if (!oldRoom) return 0;
return oldRoom.getUnreadNotificationCount('highlight');
- },
+ }
- _onHiddenHighlightsClick: function() {
+ _onHiddenHighlightsClick = () => {
const oldRoom = this._getOldRoom();
if (!oldRoom) return;
dis.dispatch({action: "view_room", room_id: oldRoom.roomId});
- },
+ };
- render: function() {
+ render() {
const RoomHeader = sdk.getComponent('rooms.RoomHeader');
const ForwardMessage = sdk.getComponent("rooms.ForwardMessage");
const AuxPanel = sdk.getComponent("rooms.AuxPanel");
@@ -2068,7 +2066,7 @@ export default createReactClass({
const showRightPanel = !forceHideRightPanel && this.state.room && this.state.showRightPanel;
const rightPanel = showRightPanel
- ?
+ ?
: null;
const timelineClasses = classNames("mx_RoomView_timeline", {
@@ -2119,5 +2117,5 @@ export default createReactClass({
);
- },
-});
+ }
+}
diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js
index 1e34e7e2ff..b4f5195803 100644
--- a/src/components/structures/ScrollPanel.js
+++ b/src/components/structures/ScrollPanel.js
@@ -15,7 +15,6 @@ limitations under the License.
*/
import React, {createRef} from "react";
-import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { Key } from '../../Keyboard';
import Timer from '../../utils/Timer';
@@ -84,10 +83,8 @@ if (DEBUG_SCROLL) {
* offset as normal.
*/
-export default createReactClass({
- displayName: 'ScrollPanel',
-
- propTypes: {
+export default class ScrollPanel extends React.Component {
+ static propTypes = {
/* stickyBottom: if set to true, then once the user hits the bottom of
* the list, any new children added to the list will cause the list to
* scroll down to show the new element, rather than preserving the
@@ -97,7 +94,7 @@ export default createReactClass({
/* startAtBottom: if set to true, the view is assumed to start
* scrolled to the bottom.
- * XXX: It's likley this is unecessary and can be derived from
+ * XXX: It's likely this is unnecessary and can be derived from
* stickyBottom, but I'm adding an extra parameter to ensure
* behaviour stays the same for other uses of ScrollPanel.
* If so, let's remove this parameter down the line.
@@ -141,6 +138,7 @@ export default createReactClass({
/* style: styles to add to the top-level div
*/
style: PropTypes.object,
+
/* resizeNotifier: ResizeNotifier to know when middle column has changed size
*/
resizeNotifier: PropTypes.object,
@@ -149,20 +147,19 @@ export default createReactClass({
* of the wrapper
*/
fixedChildren: PropTypes.node,
- },
+ };
- getDefaultProps: function() {
- return {
- stickyBottom: true,
- startAtBottom: true,
- onFillRequest: function(backwards) { return Promise.resolve(false); },
- onUnfillRequest: function(backwards, scrollToken) {},
- onScroll: function() {},
- };
- },
+ static defaultProps = {
+ stickyBottom: true,
+ startAtBottom: true,
+ onFillRequest: function(backwards) { return Promise.resolve(false); },
+ onUnfillRequest: function(backwards, scrollToken) {},
+ onScroll: function() {},
+ };
+
+ constructor(props) {
+ super(props);
- // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
- UNSAFE_componentWillMount: function() {
this._pendingFillRequests = {b: null, f: null};
if (this.props.resizeNotifier) {
@@ -172,13 +169,13 @@ export default createReactClass({
this.resetScrollState();
this._itemlist = createRef();
- },
+ }
- componentDidMount: function() {
+ componentDidMount() {
this.checkScroll();
- },
+ }
- componentDidUpdate: function() {
+ componentDidUpdate() {
// after adding event tiles, we may need to tweak the scroll (either to
// keep at the bottom of the timeline, or to maintain the view after
// adding events to the top).
@@ -186,9 +183,9 @@ export default createReactClass({
// This will also re-check the fill state, in case the paginate was inadequate
this.checkScroll();
this.updatePreventShrinking();
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
// set a boolean to say we've been unmounted, which any pending
// promises can use to throw away their results.
//
@@ -198,9 +195,9 @@ export default createReactClass({
if (this.props.resizeNotifier) {
this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize);
}
- },
+ }
- onScroll: function(ev) {
+ onScroll = ev => {
if (this.props.resizeNotifier.isResizing) return; // skip scroll events caused by resizing
debuglog("onScroll", this._getScrollNode().scrollTop);
this._scrollTimeout.restart();
@@ -208,40 +205,40 @@ export default createReactClass({
this.updatePreventShrinking();
this.props.onScroll(ev);
this.checkFillState();
- },
+ };
- onResize: function() {
+ onResize = () => {
debuglog("onResize");
this.checkScroll();
// update preventShrinkingState if present
if (this.preventShrinkingState) {
this.preventShrinking();
}
- },
+ };
// after an update to the contents of the panel, check that the scroll is
// where it ought to be, and set off pagination requests if necessary.
- checkScroll: function() {
+ checkScroll = () => {
if (this.unmounted) {
return;
}
this._restoreSavedScrollState();
this.checkFillState();
- },
+ };
// return true if the content is fully scrolled down right now; else false.
//
// note that this is independent of the 'stuckAtBottom' state - it is simply
// about whether the content is scrolled down right now, irrespective of
// whether it will stay that way when the children update.
- isAtBottom: function() {
+ isAtBottom = () => {
const sn = this._getScrollNode();
// fractional values (both too big and too small)
// for scrollTop happen on certain browsers/platforms
// when scrolled all the way down. E.g. Chrome 72 on debian.
// so check difference <= 1;
return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1;
- },
+ };
// returns the vertical height in the given direction that can be removed from
// the content box (which has a height of scrollHeight, see checkFillState) without
@@ -274,7 +271,7 @@ export default createReactClass({
// |#########| - |
// |#########| |
// `---------' -
- _getExcessHeight: function(backwards) {
+ _getExcessHeight(backwards) {
const sn = this._getScrollNode();
const contentHeight = this._getMessagesHeight();
const listHeight = this._getListHeight();
@@ -286,10 +283,10 @@ export default createReactClass({
} else {
return contentHeight - (unclippedScrollTop + 2*sn.clientHeight) - UNPAGINATION_PADDING;
}
- },
+ }
// check the scroll state and send out backfill requests if necessary.
- checkFillState: async function(depth=0) {
+ checkFillState = async (depth=0) => {
if (this.unmounted) {
return;
}
@@ -369,10 +366,10 @@ export default createReactClass({
this._fillRequestWhileRunning = false;
this.checkFillState();
}
- },
+ };
// check if unfilling is possible and send an unfill request if necessary
- _checkUnfillState: function(backwards) {
+ _checkUnfillState(backwards) {
let excessHeight = this._getExcessHeight(backwards);
if (excessHeight <= 0) {
return;
@@ -418,10 +415,10 @@ export default createReactClass({
this.props.onUnfillRequest(backwards, markerScrollToken);
}, UNFILL_REQUEST_DEBOUNCE_MS);
}
- },
+ }
// check if there is already a pending fill request. If not, set one off.
- _maybeFill: function(depth, backwards) {
+ _maybeFill(depth, backwards) {
const dir = backwards ? 'b' : 'f';
if (this._pendingFillRequests[dir]) {
debuglog("Already a "+dir+" fill in progress - not starting another");
@@ -457,7 +454,7 @@ export default createReactClass({
return this.checkFillState(depth + 1);
}
});
- },
+ }
/* get the current scroll state. This returns an object with the following
* properties:
@@ -473,9 +470,7 @@ export default createReactClass({
* the number of pixels the bottom of the tracked child is above the
* bottom of the scroll panel.
*/
- getScrollState: function() {
- return this.scrollState;
- },
+ getScrollState = () => this.scrollState;
/* reset the saved scroll state.
*
@@ -489,7 +484,7 @@ export default createReactClass({
* no use if no children exist yet, or if you are about to replace the
* child list.)
*/
- resetScrollState: function() {
+ resetScrollState = () => {
this.scrollState = {
stuckAtBottom: this.props.startAtBottom,
};
@@ -497,20 +492,20 @@ export default createReactClass({
this._pages = 0;
this._scrollTimeout = new Timer(100);
this._heightUpdateInProgress = false;
- },
+ };
/**
* jump to the top of the content.
*/
- scrollToTop: function() {
+ scrollToTop = () => {
this._getScrollNode().scrollTop = 0;
this._saveScrollState();
- },
+ };
/**
* jump to the bottom of the content.
*/
- scrollToBottom: function() {
+ scrollToBottom = () => {
// the easiest way to make sure that the scroll state is correctly
// saved is to do the scroll, then save the updated state. (Calculating
// it ourselves is hard, and we can't rely on an onScroll callback
@@ -518,25 +513,25 @@ export default createReactClass({
const sn = this._getScrollNode();
sn.scrollTop = sn.scrollHeight;
this._saveScrollState();
- },
+ };
/**
* Page up/down.
*
* @param {number} mult: -1 to page up, +1 to page down
*/
- scrollRelative: function(mult) {
+ scrollRelative = mult => {
const scrollNode = this._getScrollNode();
const delta = mult * scrollNode.clientHeight * 0.5;
scrollNode.scrollBy(0, delta);
this._saveScrollState();
- },
+ };
/**
* Scroll up/down in response to a scroll key
* @param {object} ev the keyboard event
*/
- handleScrollKey: function(ev) {
+ handleScrollKey = ev => {
switch (ev.key) {
case Key.PAGE_UP:
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
@@ -562,7 +557,7 @@ export default createReactClass({
}
break;
}
- },
+ };
/* Scroll the panel to bring the DOM node with the scroll token
* `scrollToken` into view.
@@ -575,7 +570,7 @@ export default createReactClass({
* node (specifically, the bottom of it) will be positioned. If omitted, it
* defaults to 0.
*/
- scrollToToken: function(scrollToken, pixelOffset, offsetBase) {
+ scrollToToken = (scrollToken, pixelOffset, offsetBase) => {
pixelOffset = pixelOffset || 0;
offsetBase = offsetBase || 0;
@@ -597,9 +592,9 @@ export default createReactClass({
scrollNode.scrollTop = (trackedNode.offsetTop - (scrollNode.clientHeight * offsetBase)) + pixelOffset;
this._saveScrollState();
}
- },
+ };
- _saveScrollState: function() {
+ _saveScrollState() {
if (this.props.stickyBottom && this.isAtBottom()) {
this.scrollState = { stuckAtBottom: true };
debuglog("saved stuckAtBottom state");
@@ -642,9 +637,9 @@ export default createReactClass({
bottomOffset: bottomOffset,
pixelOffset: bottomOffset - viewportBottom, //needed for restoring the scroll position when coming back to the room
};
- },
+ }
- _restoreSavedScrollState: async function() {
+ async _restoreSavedScrollState() {
const scrollState = this.scrollState;
if (scrollState.stuckAtBottom) {
@@ -677,7 +672,8 @@ export default createReactClass({
} else {
debuglog("not updating height because request already in progress");
}
- },
+ }
+
// need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content?
async _updateHeight() {
// wait until user has stopped scrolling
@@ -732,7 +728,7 @@ export default createReactClass({
debuglog("updateHeight to", {newHeight, topDiff});
}
}
- },
+ }
_getTrackedNode() {
const scrollState = this.scrollState;
@@ -765,11 +761,11 @@ export default createReactClass({
}
return scrollState.trackedNode;
- },
+ }
_getListHeight() {
return this._bottomGrowth + (this._pages * PAGE_SIZE);
- },
+ }
_getMessagesHeight() {
const itemlist = this._itemlist.current;
@@ -778,17 +774,17 @@ export default createReactClass({
const firstNodeTop = itemlist.firstElementChild ? itemlist.firstElementChild.offsetTop : 0;
// 18 is itemlist padding
return lastNodeBottom - firstNodeTop + (18 * 2);
- },
+ }
_topFromBottom(node) {
// current capped height - distance from top = distance from bottom of container to top of tracked element
return this._itemlist.current.clientHeight - node.offsetTop;
- },
+ }
/* get the DOM node which has the scrollTop property we care about for our
* message panel.
*/
- _getScrollNode: function() {
+ _getScrollNode() {
if (this.unmounted) {
// this shouldn't happen, but when it does, turn the NPE into
// something more meaningful.
@@ -802,18 +798,18 @@ export default createReactClass({
}
return this._divScroll;
- },
+ }
- _collectScroll: function(divScroll) {
+ _collectScroll = divScroll => {
this._divScroll = divScroll;
- },
+ };
/**
Mark the bottom offset of the last tile so we can balance it out when
anything below it changes, by calling updatePreventShrinking, to keep
the same minimum bottom offset, effectively preventing the timeline to shrink.
*/
- preventShrinking: function() {
+ preventShrinking = () => {
const messageList = this._itemlist.current;
const tiles = messageList && messageList.children;
if (!messageList) {
@@ -837,16 +833,16 @@ export default createReactClass({
offsetNode: lastTileNode,
};
debuglog("prevent shrinking, last tile ", offsetFromBottom, "px from bottom");
- },
+ };
/** Clear shrinking prevention. Used internally, and when the timeline is reloaded. */
- clearPreventShrinking: function() {
+ clearPreventShrinking = () => {
const messageList = this._itemlist.current;
const balanceElement = messageList && messageList.parentElement;
if (balanceElement) balanceElement.style.paddingBottom = null;
this.preventShrinkingState = null;
debuglog("prevent shrinking cleared");
- },
+ };
/**
update the container padding to balance
@@ -856,7 +852,7 @@ export default createReactClass({
from the bottom of the marked tile grows larger than
what it was when marking.
*/
- updatePreventShrinking: function() {
+ updatePreventShrinking = () => {
if (this.preventShrinkingState) {
const sn = this._getScrollNode();
const scrollState = this.scrollState;
@@ -886,9 +882,9 @@ export default createReactClass({
this.clearPreventShrinking();
}
}
- },
+ };
- render: function() {
+ render() {
// TODO: the classnames on the div and ol could do with being updated to
// reflect the fact that we don't necessarily contain a list of messages.
// it's not obvious why we have a separate div and ol anyway.
@@ -906,5 +902,5 @@ export default createReactClass({
);
- },
-});
+ }
+}
diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js
index 7e9d290bce..c1e3ad0cf2 100644
--- a/src/components/structures/SearchBox.js
+++ b/src/components/structures/SearchBox.js
@@ -16,18 +16,15 @@ limitations under the License.
*/
import React, {createRef} from 'react';
-import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { Key } from '../../Keyboard';
import dis from '../../dispatcher/dispatcher';
-import { throttle } from 'lodash';
+import {throttle} from 'lodash';
import AccessibleButton from '../../components/views/elements/AccessibleButton';
import classNames from 'classnames';
-export default createReactClass({
- displayName: 'SearchBox',
-
- propTypes: {
+export default class SearchBox extends React.Component {
+ static propTypes = {
onSearch: PropTypes.func,
onCleared: PropTypes.func,
onKeyDown: PropTypes.func,
@@ -38,35 +35,32 @@ export default createReactClass({
// on room search focus action (it would be nicer to take
// this functionality out, but not obvious how that would work)
enableRoomSearchFocus: PropTypes.bool,
- },
+ };
- getDefaultProps: function() {
- return {
- enableRoomSearchFocus: false,
- };
- },
+ static defaultProps = {
+ enableRoomSearchFocus: false,
+ };
- getInitialState: function() {
- return {
+ constructor(props) {
+ super(props);
+
+ this._search = createRef();
+
+ this.state = {
searchTerm: "",
blurred: true,
};
- },
+ }
- // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
- UNSAFE_componentWillMount: function() {
- this._search = createRef();
- },
-
- componentDidMount: function() {
+ componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
dis.unregister(this.dispatcherRef);
- },
+ }
- onAction: function(payload) {
+ onAction = payload => {
if (!this.props.enableRoomSearchFocus) return;
switch (payload.action) {
@@ -81,51 +75,51 @@ export default createReactClass({
}
break;
}
- },
+ };
- onChange: function() {
+ onChange = () => {
if (!this._search.current) return;
this.setState({ searchTerm: this._search.current.value });
this.onSearch();
- },
+ };
- onSearch: throttle(function() {
+ onSearch = throttle(() => {
this.props.onSearch(this._search.current.value);
- }, 200, {trailing: true, leading: true}),
+ }, 200, {trailing: true, leading: true});
- _onKeyDown: function(ev) {
+ _onKeyDown = ev => {
switch (ev.key) {
case Key.ESCAPE:
this._clearSearch("keyboard");
break;
}
if (this.props.onKeyDown) this.props.onKeyDown(ev);
- },
+ };
- _onFocus: function(ev) {
+ _onFocus = ev => {
this.setState({blurred: false});
ev.target.select();
if (this.props.onFocus) {
this.props.onFocus(ev);
}
- },
+ };
- _onBlur: function(ev) {
+ _onBlur = ev => {
this.setState({blurred: true});
if (this.props.onBlur) {
this.props.onBlur(ev);
}
- },
+ };
- _clearSearch: function(source) {
+ _clearSearch(source) {
this._search.current.value = "";
this.onChange();
if (this.props.onCleared) {
this.props.onCleared(source);
}
- },
+ }
- render: function() {
+ render() {
// check for collapsed here and
// not at parent so we keep
// searchTerm in our state
@@ -166,5 +160,5 @@ export default createReactClass({
{ clearButton }
);
- },
-});
+ }
+}
diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js
index a714b126ec..135b2a1c5c 100644
--- a/src/components/structures/TagPanel.js
+++ b/src/components/structures/TagPanel.js
@@ -16,7 +16,6 @@ limitations under the License.
*/
import React from 'react';
-import createReactClass from 'create-react-class';
import TagOrderStore from '../../stores/TagOrderStore';
import GroupActions from '../../actions/GroupActions';
@@ -32,21 +31,15 @@ import AutoHideScrollbar from "./AutoHideScrollbar";
import SettingsStore from "../../settings/SettingsStore";
import UserTagTile from "../views/elements/UserTagTile";
-const TagPanel = createReactClass({
- displayName: 'TagPanel',
+class TagPanel extends React.Component {
+ static contextType = MatrixClientContext;
- statics: {
- contextType: MatrixClientContext,
- },
+ state = {
+ orderedTags: [],
+ selectedTags: [],
+ };
- getInitialState() {
- return {
- orderedTags: [],
- selectedTags: [],
- };
- },
-
- componentDidMount: function() {
+ componentDidMount() {
this.unmounted = false;
this.context.on("Group.myMembership", this._onGroupMyMembership);
this.context.on("sync", this._onClientSync);
@@ -62,7 +55,7 @@ const TagPanel = createReactClass({
});
// This could be done by anything with a matrix client
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
- },
+ }
componentWillUnmount() {
this.unmounted = true;
@@ -71,14 +64,14 @@ const TagPanel = createReactClass({
if (this._tagOrderStoreToken) {
this._tagOrderStoreToken.remove();
}
- },
+ }
- _onGroupMyMembership() {
+ _onGroupMyMembership = () => {
if (this.unmounted) return;
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
- },
+ };
- _onClientSync(syncState, prevState) {
+ _onClientSync = (syncState, prevState) => {
// Consider the client reconnected if there is no error with syncing.
// This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
const reconnected = syncState !== "ERROR" && prevState !== syncState;
@@ -86,18 +79,18 @@ const TagPanel = createReactClass({
// Load joined groups
dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
}
- },
+ };
- onMouseDown(e) {
+ onMouseDown = e => {
// only dispatch if its not a no-op
if (this.state.selectedTags.length > 0) {
dis.dispatch({action: 'deselect_tags'});
}
- },
+ };
- onClearFilterClick(ev) {
+ onClearFilterClick = ev => {
dis.dispatch({action: 'deselect_tags'});
- },
+ };
renderGlobalIcon() {
if (!SettingsStore.getValue("feature_communities_v2_prototypes")) return null;
@@ -108,7 +101,7 @@ const TagPanel = createReactClass({
);
- },
+ }
render() {
const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
@@ -173,6 +166,6 @@ const TagPanel = createReactClass({
;
- },
-});
+ }
+}
export default TagPanel;
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index 2313b60ab1..daa18bb290 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -19,7 +19,6 @@ limitations under the License.
import SettingsStore from "../../settings/SettingsStore";
import React, {createRef} from 'react';
-import createReactClass from 'create-react-class';
import ReactDOM from "react-dom";
import PropTypes from 'prop-types';
import {EventTimeline} from "matrix-js-sdk";
@@ -54,10 +53,8 @@ if (DEBUG) {
*
* Also responsible for handling and sending read receipts.
*/
-const TimelinePanel = createReactClass({
- displayName: 'TimelinePanel',
-
- propTypes: {
+class TimelinePanel extends React.Component {
+ static propTypes = {
// The js-sdk EventTimelineSet object for the timeline sequence we are
// representing. This may or may not have a room, depending on what it's
// a timeline representing. If it has a room, we maintain RRs etc for
@@ -115,23 +112,28 @@ const TimelinePanel = createReactClass({
// whether to use the irc layout
useIRCLayout: PropTypes.bool,
- },
+ }
- statics: {
- // a map from room id to read marker event timestamp
- roomReadMarkerTsMap: {},
- },
+ // a map from room id to read marker event timestamp
+ static roomReadMarkerTsMap = {};
- getDefaultProps: function() {
- return {
- // By default, disable the timelineCap in favour of unpaginating based on
- // event tile heights. (See _unpaginateEvents)
- timelineCap: Number.MAX_VALUE,
- className: 'mx_RoomView_messagePanel',
- };
- },
+ static defaultProps = {
+ // By default, disable the timelineCap in favour of unpaginating based on
+ // event tile heights. (See _unpaginateEvents)
+ timelineCap: Number.MAX_VALUE,
+ className: 'mx_RoomView_messagePanel',
+ };
+
+ constructor(props) {
+ super(props);
+
+ debuglog("TimelinePanel: mounting");
+
+ this.lastRRSentEventId = undefined;
+ this.lastRMSentEventId = undefined;
+
+ this._messagePanel = createRef();
- getInitialState: function() {
// XXX: we could track RM per TimelineSet rather than per Room.
// but for now we just do it per room for simplicity.
let initialReadMarker = null;
@@ -144,7 +146,7 @@ const TimelinePanel = createReactClass({
}
}
- return {
+ this.state = {
events: [],
liveEvents: [],
timelineLoading: true, // track whether our room timeline is loading
@@ -203,24 +205,6 @@ const TimelinePanel = createReactClass({
// how long to show the RM for when it's scrolled off-screen
readMarkerOutOfViewThresholdMs: SettingsStore.getValue("readMarkerOutOfViewThresholdMs"),
};
- },
-
- // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
- UNSAFE_componentWillMount: function() {
- debuglog("TimelinePanel: mounting");
-
- this.lastRRSentEventId = undefined;
- this.lastRMSentEventId = undefined;
-
- this._messagePanel = createRef();
-
- if (this.props.manageReadReceipts) {
- this.updateReadReceiptOnUserActivity();
- }
- if (this.props.manageReadMarkers) {
- this.updateReadMarkerOnUserActivity();
- }
-
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
@@ -234,12 +218,24 @@ const TimelinePanel = createReactClass({
MatrixClientPeg.get().on("Event.decrypted", this.onEventDecrypted);
MatrixClientPeg.get().on("Event.replaced", this.onEventReplaced);
MatrixClientPeg.get().on("sync", this.onSync);
+ }
+
+ // TODO: [REACT-WARNING] Move into constructor
+ // eslint-disable-next-line camelcase
+ UNSAFE_componentWillMount() {
+ if (this.props.manageReadReceipts) {
+ this.updateReadReceiptOnUserActivity();
+ }
+ if (this.props.manageReadMarkers) {
+ this.updateReadMarkerOnUserActivity();
+ }
this._initTimeline(this.props);
- },
+ }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
- UNSAFE_componentWillReceiveProps: function(newProps) {
+ // eslint-disable-next-line camelcase
+ UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.timelineSet !== this.props.timelineSet) {
// throw new Error("changing timelineSet on a TimelinePanel is not supported");
@@ -260,9 +256,9 @@ const TimelinePanel = createReactClass({
" (was " + this.props.eventId + ")");
return this._initTimeline(newProps);
}
- },
+ }
- shouldComponentUpdate: function(nextProps, nextState) {
+ shouldComponentUpdate(nextProps, nextState) {
if (!ObjectUtils.shallowEqual(this.props, nextProps)) {
if (DEBUG) {
console.group("Timeline.shouldComponentUpdate: props change");
@@ -284,9 +280,9 @@ const TimelinePanel = createReactClass({
}
return false;
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
// set a boolean to say we've been unmounted, which any pending
// promises can use to throw away their results.
//
@@ -316,9 +312,9 @@ const TimelinePanel = createReactClass({
client.removeListener("Event.replaced", this.onEventReplaced);
client.removeListener("sync", this.onSync);
}
- },
+ }
- onMessageListUnfillRequest: function(backwards, scrollToken) {
+ onMessageListUnfillRequest = (backwards, scrollToken) => {
// If backwards, unpaginate from the back (i.e. the start of the timeline)
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
debuglog("TimelinePanel: unpaginating events in direction", dir);
@@ -349,18 +345,18 @@ const TimelinePanel = createReactClass({
firstVisibleEventIndex,
});
}
- },
+ };
- onPaginationRequest(timelineWindow, direction, size) {
+ onPaginationRequest = (timelineWindow, direction, size) => {
if (this.props.onPaginationRequest) {
return this.props.onPaginationRequest(timelineWindow, direction, size);
} else {
return timelineWindow.paginate(direction, size);
}
- },
+ };
// set off a pagination request.
- onMessageListFillRequest: function(backwards) {
+ onMessageListFillRequest = backwards => {
if (!this._shouldPaginate()) return Promise.resolve(false);
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
@@ -425,9 +421,9 @@ const TimelinePanel = createReactClass({
});
});
});
- },
+ };
- onMessageListScroll: function(e) {
+ onMessageListScroll = e => {
if (this.props.onScroll) {
this.props.onScroll(e);
}
@@ -447,9 +443,9 @@ const TimelinePanel = createReactClass({
// NO-OP when timeout already has set to the given value
this._readMarkerActivityTimer.changeTimeout(timeout);
}
- },
+ };
- onAction: function(payload) {
+ onAction = payload => {
if (payload.action === 'ignore_state_changed') {
this.forceUpdate();
}
@@ -463,9 +459,9 @@ const TimelinePanel = createReactClass({
}
});
}
- },
+ };
- onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) {
+ onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => {
// ignore events for other timeline sets
if (data.timeline.getTimelineSet() !== this.props.timelineSet) return;
@@ -537,21 +533,19 @@ const TimelinePanel = createReactClass({
}
});
});
- },
+ };
- onRoomTimelineReset: function(room, timelineSet) {
+ onRoomTimelineReset = (room, timelineSet) => {
if (timelineSet !== this.props.timelineSet) return;
if (this._messagePanel.current && this._messagePanel.current.isAtBottom()) {
this._loadTimeline();
}
- },
+ };
- canResetTimeline: function() {
- return this._messagePanel.current && this._messagePanel.current.isAtBottom();
- },
+ canResetTimeline = () => this._messagePanel.current && this._messagePanel.current.isAtBottom();
- onRoomRedaction: function(ev, room) {
+ onRoomRedaction = (ev, room) => {
if (this.unmounted) return;
// ignore events for other rooms
@@ -560,9 +554,9 @@ const TimelinePanel = createReactClass({
// we could skip an update if the event isn't in our timeline,
// but that's probably an early optimisation.
this.forceUpdate();
- },
+ };
- onEventReplaced: function(replacedEvent, room) {
+ onEventReplaced = (replacedEvent, room) => {
if (this.unmounted) return;
// ignore events for other rooms
@@ -571,27 +565,27 @@ const TimelinePanel = createReactClass({
// we could skip an update if the event isn't in our timeline,
// but that's probably an early optimisation.
this.forceUpdate();
- },
+ };
- onRoomReceipt: function(ev, room) {
+ onRoomReceipt = (ev, room) => {
if (this.unmounted) return;
// ignore events for other rooms
if (room !== this.props.timelineSet.room) return;
this.forceUpdate();
- },
+ };
- onLocalEchoUpdated: function(ev, room, oldEventId) {
+ onLocalEchoUpdated = (ev, room, oldEventId) => {
if (this.unmounted) return;
// ignore events for other rooms
if (room !== this.props.timelineSet.room) return;
this._reloadEvents();
- },
+ };
- onAccountData: function(ev, room) {
+ onAccountData = (ev, room) => {
if (this.unmounted) return;
// ignore events for other rooms
@@ -605,9 +599,9 @@ const TimelinePanel = createReactClass({
this.setState({
readMarkerEventId: ev.getContent().event_id,
}, this.props.onReadMarkerUpdated);
- },
+ };
- onEventDecrypted: function(ev) {
+ onEventDecrypted = ev => {
// Can be null for the notification timeline, etc.
if (!this.props.timelineSet.room) return;
@@ -620,19 +614,19 @@ const TimelinePanel = createReactClass({
if (ev.getRoomId() === this.props.timelineSet.room.roomId) {
this.forceUpdate();
}
- },
+ };
- onSync: function(state, prevState, data) {
+ onSync = (state, prevState, data) => {
this.setState({clientSyncState: state});
- },
+ };
_readMarkerTimeout(readMarkerPosition) {
return readMarkerPosition === 0 ?
this.state.readMarkerInViewThresholdMs :
this.state.readMarkerOutOfViewThresholdMs;
- },
+ }
- updateReadMarkerOnUserActivity: async function() {
+ async updateReadMarkerOnUserActivity() {
const initialTimeout = this._readMarkerTimeout(this.getReadMarkerPosition());
this._readMarkerActivityTimer = new Timer(initialTimeout);
@@ -644,9 +638,9 @@ const TimelinePanel = createReactClass({
// outside of try/catch to not swallow errors
this.updateReadMarker();
}
- },
+ }
- updateReadReceiptOnUserActivity: async function() {
+ async updateReadReceiptOnUserActivity() {
this._readReceiptActivityTimer = new Timer(READ_RECEIPT_INTERVAL_MS);
while (this._readReceiptActivityTimer) { //unset on unmount
UserActivity.sharedInstance().timeWhileActiveNow(this._readReceiptActivityTimer);
@@ -656,9 +650,9 @@ const TimelinePanel = createReactClass({
// outside of try/catch to not swallow errors
this.sendReadReceipt();
}
- },
+ }
- sendReadReceipt: function() {
+ sendReadReceipt = () => {
if (SettingsStore.getValue("lowBandwidth")) return;
if (!this._messagePanel.current) return;
@@ -766,11 +760,11 @@ const TimelinePanel = createReactClass({
});
}
}
- },
+ };
// if the read marker is on the screen, we can now assume we've caught up to the end
// of the screen, so move the marker down to the bottom of the screen.
- updateReadMarker: function() {
+ updateReadMarker = () => {
if (!this.props.manageReadMarkers) return;
if (this.getReadMarkerPosition() === 1) {
// the read marker is at an event below the viewport,
@@ -801,11 +795,11 @@ const TimelinePanel = createReactClass({
// Send the updated read marker (along with read receipt) to the server
this.sendReadReceipt();
- },
+ };
// advance the read marker past any events we sent ourselves.
- _advanceReadMarkerPastMyEvents: function() {
+ _advanceReadMarkerPastMyEvents() {
if (!this.props.manageReadMarkers) return;
// we call `_timelineWindow.getEvents()` rather than using
@@ -837,11 +831,11 @@ const TimelinePanel = createReactClass({
const ev = events[i];
this._setReadMarker(ev.getId(), ev.getTs());
- },
+ }
/* jump down to the bottom of this room, where new events are arriving
*/
- jumpToLiveTimeline: function() {
+ jumpToLiveTimeline = () => {
// if we can't forward-paginate the existing timeline, then there
// is no point reloading it - just jump straight to the bottom.
//
@@ -854,12 +848,12 @@ const TimelinePanel = createReactClass({
this._messagePanel.current.scrollToBottom();
}
}
- },
+ };
/* scroll to show the read-up-to marker. We put it 1/3 of the way down
* the container.
*/
- jumpToReadMarker: function() {
+ jumpToReadMarker = () => {
if (!this.props.manageReadMarkers) return;
if (!this._messagePanel.current) return;
if (!this.state.readMarkerEventId) return;
@@ -883,11 +877,11 @@ const TimelinePanel = createReactClass({
// As with jumpToLiveTimeline, we want to reload the timeline around the
// read-marker.
this._loadTimeline(this.state.readMarkerEventId, 0, 1/3);
- },
+ };
/* update the read-up-to marker to match the read receipt
*/
- forgetReadMarker: function() {
+ forgetReadMarker = () => {
if (!this.props.manageReadMarkers) return;
const rmId = this._getCurrentReadReceipt();
@@ -903,17 +897,17 @@ const TimelinePanel = createReactClass({
}
this._setReadMarker(rmId, rmTs);
- },
+ };
/* return true if the content is fully scrolled down and we are
* at the end of the live timeline.
*/
- isAtEndOfLiveTimeline: function() {
+ isAtEndOfLiveTimeline = () => {
return this._messagePanel.current
&& this._messagePanel.current.isAtBottom()
&& this._timelineWindow
&& !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
- },
+ }
/* get the current scroll state. See ScrollPanel.getScrollState for
@@ -921,10 +915,10 @@ const TimelinePanel = createReactClass({
*
* returns null if we are not mounted.
*/
- getScrollState: function() {
+ getScrollState = () => {
if (!this._messagePanel.current) { return null; }
return this._messagePanel.current.getScrollState();
- },
+ };
// returns one of:
//
@@ -932,7 +926,7 @@ const TimelinePanel = createReactClass({
// -1: read marker is above the window
// 0: read marker is visible
// +1: read marker is below the window
- getReadMarkerPosition: function() {
+ getReadMarkerPosition = () => {
if (!this.props.manageReadMarkers) return null;
if (!this._messagePanel.current) return null;
@@ -953,9 +947,9 @@ const TimelinePanel = createReactClass({
}
return null;
- },
+ };
- canJumpToReadMarker: function() {
+ canJumpToReadMarker = () => {
// 1. Do not show jump bar if neither the RM nor the RR are set.
// 3. We want to show the bar if the read-marker is off the top of the screen.
// 4. Also, if pos === null, the event might not be paginated - show the unread bar
@@ -963,14 +957,14 @@ const TimelinePanel = createReactClass({
const ret = this.state.readMarkerEventId !== null && // 1.
(pos < 0 || pos === null); // 3., 4.
return ret;
- },
+ };
/*
* called by the parent component when PageUp/Down/etc is pressed.
*
* We pass it down to the scroll panel.
*/
- handleScrollKey: function(ev) {
+ handleScrollKey = ev => {
if (!this._messagePanel.current) { return; }
// jump to the live timeline on ctrl-end, rather than the end of the
@@ -980,9 +974,9 @@ const TimelinePanel = createReactClass({
} else {
this._messagePanel.current.handleScrollKey(ev);
}
- },
+ };
- _initTimeline: function(props) {
+ _initTimeline(props) {
const initialEvent = props.eventId;
const pixelOffset = props.eventPixelOffset;
@@ -994,7 +988,7 @@ const TimelinePanel = createReactClass({
}
return this._loadTimeline(initialEvent, pixelOffset, offsetBase);
- },
+ }
/**
* (re)-load the event timeline, and initialise the scroll state, centered
@@ -1012,7 +1006,7 @@ const TimelinePanel = createReactClass({
*
* returns a promise which will resolve when the load completes.
*/
- _loadTimeline: function(eventId, pixelOffset, offsetBase) {
+ _loadTimeline(eventId, pixelOffset, offsetBase) {
this._timelineWindow = new Matrix.TimelineWindow(
MatrixClientPeg.get(), this.props.timelineSet,
{windowLimit: this.props.timelineCap});
@@ -1122,21 +1116,21 @@ const TimelinePanel = createReactClass({
});
prom.then(onLoaded, onError);
}
- },
+ }
// handle the completion of a timeline load or localEchoUpdate, by
// reloading the events from the timelinewindow and pending event list into
// the state.
- _reloadEvents: function() {
+ _reloadEvents() {
// we might have switched rooms since the load started - just bin
// the results if so.
if (this.unmounted) return;
this.setState(this._getEvents());
- },
+ }
// get the list of events from the timeline window and the pending event list
- _getEvents: function() {
+ _getEvents() {
const events = this._timelineWindow.getEvents();
const firstVisibleEventIndex = this._checkForPreJoinUISI(events);
@@ -1154,7 +1148,7 @@ const TimelinePanel = createReactClass({
liveEvents,
firstVisibleEventIndex,
};
- },
+ }
/**
* Check for undecryptable messages that were sent while the user was not in
@@ -1166,7 +1160,7 @@ const TimelinePanel = createReactClass({
* undecryptable event that was sent while the user was not in the room. If no
* such events were found, then it returns 0.
*/
- _checkForPreJoinUISI: function(events) {
+ _checkForPreJoinUISI(events) {
const room = this.props.timelineSet.room;
if (events.length === 0 || !room ||
@@ -1228,18 +1222,18 @@ const TimelinePanel = createReactClass({
}
}
return 0;
- },
+ }
- _indexForEventId: function(evId) {
+ _indexForEventId(evId) {
for (let i = 0; i < this.state.events.length; ++i) {
if (evId == this.state.events[i].getId()) {
return i;
}
}
return null;
- },
+ }
- _getLastDisplayedEventIndex: function(opts) {
+ _getLastDisplayedEventIndex(opts) {
opts = opts || {};
const ignoreOwn = opts.ignoreOwn || false;
const allowPartial = opts.allowPartial || false;
@@ -1313,7 +1307,7 @@ const TimelinePanel = createReactClass({
}
return null;
- },
+ }
/**
* Get the id of the event corresponding to our user's latest read-receipt.
@@ -1324,7 +1318,7 @@ const TimelinePanel = createReactClass({
* SDK.
* @return {String} the event ID
*/
- _getCurrentReadReceipt: function(ignoreSynthesized) {
+ _getCurrentReadReceipt(ignoreSynthesized) {
const client = MatrixClientPeg.get();
// the client can be null on logout
if (client == null) {
@@ -1333,9 +1327,9 @@ const TimelinePanel = createReactClass({
const myUserId = client.credentials.userId;
return this.props.timelineSet.room.getEventReadUpTo(myUserId, ignoreSynthesized);
- },
+ }
- _setReadMarker: function(eventId, eventTs, inhibitSetState) {
+ _setReadMarker(eventId, eventTs, inhibitSetState) {
const roomId = this.props.timelineSet.room.roomId;
// don't update the state (and cause a re-render) if there is
@@ -1358,9 +1352,9 @@ const TimelinePanel = createReactClass({
this.setState({
readMarkerEventId: eventId,
}, this.props.onReadMarkerUpdated);
- },
+ }
- _shouldPaginate: function() {
+ _shouldPaginate() {
// don't try to paginate while events in the timeline are
// still being decrypted. We don't render events while they're
// being decrypted, so they don't take up space in the timeline.
@@ -1369,13 +1363,11 @@ const TimelinePanel = createReactClass({
return !this.state.events.some((e) => {
return e.isBeingDecrypted();
});
- },
+ }
- getRelationsForEvent(...args) {
- return this.props.timelineSet.getRelationsForEvent(...args);
- },
+ getRelationsForEvent = (...args) => this.props.timelineSet.getRelationsForEvent(...args);
- render: function() {
+ render() {
const MessagePanel = sdk.getComponent("structures.MessagePanel");
const Loader = sdk.getComponent("elements.Spinner");
@@ -1456,7 +1448,7 @@ const TimelinePanel = createReactClass({
useIRCLayout={this.props.useIRCLayout}
/>
);
- },
-});
+ }
+}
export default TimelinePanel;
diff --git a/src/components/structures/UploadBar.js b/src/components/structures/UploadBar.js
index 421d1d79a7..0865764c5a 100644
--- a/src/components/structures/UploadBar.js
+++ b/src/components/structures/UploadBar.js
@@ -16,30 +16,28 @@ limitations under the License.
*/
import React from 'react';
-import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import ContentMessages from '../../ContentMessages';
import dis from "../../dispatcher/dispatcher";
import filesize from "filesize";
import { _t } from '../../languageHandler';
-export default createReactClass({
- displayName: 'UploadBar',
- propTypes: {
+export default class UploadBar extends React.Component {
+ static propTypes = {
room: PropTypes.object,
- },
+ };
- componentDidMount: function() {
+ componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
this.mounted = true;
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
this.mounted = false;
dis.unregister(this.dispatcherRef);
- },
+ }
- onAction: function(payload) {
+ onAction = payload => {
switch (payload.action) {
case 'upload_progress':
case 'upload_finished':
@@ -48,9 +46,9 @@ export default createReactClass({
if (this.mounted) this.forceUpdate();
break;
}
- },
+ };
- render: function() {
+ render() {
const uploads = ContentMessages.sharedInstance().getCurrentUploads();
// for testing UI... - also fix up the ContentMessages.getCurrentUploads().length
@@ -105,5 +103,5 @@ export default createReactClass({
{ uploadText }
);
- },
-});
+ }
+}
diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx
index 69b9c3f26e..b83369d296 100644
--- a/src/components/structures/UserMenu.tsx
+++ b/src/components/structures/UserMenu.tsx
@@ -42,6 +42,14 @@ import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList,
} from "../views/context_menus/IconizedContextMenu";
+import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore";
+import * as fbEmitter from "fbemitter";
+import TagOrderStore from "../../stores/TagOrderStore";
+import { showCommunityInviteDialog } from "../../RoomInvite";
+import dis from "../../dispatcher/dispatcher";
+import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
+import ErrorDialog from "../views/dialogs/ErrorDialog";
+import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototypeDialog";
interface IProps {
isMinimized: boolean;
@@ -58,6 +66,7 @@ export default class UserMenu extends React.Component {
private dispatcherRef: string;
private themeWatcherRef: string;
private buttonRef: React.RefObject = createRef();
+ private tagStoreRef: fbEmitter.EventSubscription;
constructor(props: IProps) {
super(props);
@@ -77,14 +86,20 @@ export default class UserMenu extends React.Component {
public componentDidMount() {
this.dispatcherRef = defaultDispatcher.register(this.onAction);
this.themeWatcherRef = SettingsStore.watchSetting("theme", null, this.onThemeChanged);
+ this.tagStoreRef = TagOrderStore.addListener(this.onTagStoreUpdate);
}
public componentWillUnmount() {
if (this.themeWatcherRef) SettingsStore.unwatchSetting(this.themeWatcherRef);
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate);
+ this.tagStoreRef.remove();
}
+ private onTagStoreUpdate = () => {
+ this.forceUpdate(); // we don't have anything useful in state to update
+ };
+
private isUserOnDarkTheme(): boolean {
const theme = SettingsStore.getValue("theme");
if (theme.startsWith("custom-")) {
@@ -189,9 +204,54 @@ export default class UserMenu extends React.Component {
defaultDispatcher.dispatch({action: 'view_home_page'});
};
+ private onCommunitySettingsClick = (ev: ButtonEvent) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ Modal.createTrackedDialog('Edit Community', '', EditCommunityPrototypeDialog, {
+ communityId: CommunityPrototypeStore.instance.getSelectedCommunityId(),
+ });
+ this.setState({contextMenuPosition: null}); // also close the menu
+ };
+
+ private onCommunityMembersClick = (ev: ButtonEvent) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ // We'd ideally just pop open a right panel with the member list, but the current
+ // way the right panel is structured makes this exceedingly difficult. Instead, we'll
+ // switch to the general room and open the member list there as it should be in sync
+ // anyways.
+ const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat();
+ if (chat) {
+ dis.dispatch({
+ action: 'view_room',
+ room_id: chat.roomId,
+ }, true);
+ dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList});
+ } else {
+ // "This should never happen" clauses go here for the prototype.
+ Modal.createTrackedDialog('Failed to find general chat', '', ErrorDialog, {
+ title: _t('Failed to find the general chat for this community'),
+ description: _t("Failed to find the general chat for this community"),
+ });
+ }
+ this.setState({contextMenuPosition: null}); // also close the menu
+ };
+
+ private onCommunityInviteClick = (ev: ButtonEvent) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId());
+ this.setState({contextMenuPosition: null}); // also close the menu
+ };
+
private renderContextMenu = (): React.ReactNode => {
if (!this.state.contextMenuPosition) return null;
+ const prototypeCommunityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
+
let hostingLink;
const signupLink = getHostingLink("user-context-menu");
if (signupLink) {
@@ -225,22 +285,137 @@ export default class UserMenu extends React.Component {
);
}
- return
-
- {_t("Where you’re logged in")}
{_t(
"Manage the names of and sign out of your sessions below or " +
@@ -351,11 +350,15 @@ export default class SecurityUserSettingsTab extends React.Component {
);
}
diff --git a/src/components/views/voip/VideoFeed.js b/src/components/views/voip/VideoFeed.js
index 527b071942..a0330f8cb1 100644
--- a/src/components/views/voip/VideoFeed.js
+++ b/src/components/views/voip/VideoFeed.js
@@ -17,44 +17,42 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
-export default createReactClass({
- displayName: 'VideoFeed',
-
- propTypes: {
+export default class VideoFeed extends React.Component {
+ static propTypes = {
// maxHeight style attribute for the video element
maxHeight: PropTypes.number,
// a callback which is called when the video element is resized
// due to a change in video metadata
onResize: PropTypes.func,
- },
+ };
+
+ constructor(props) {
+ super(props);
- // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
- UNSAFE_componentWillMount() {
this._vid = createRef();
- },
+ }
componentDidMount() {
this._vid.current.addEventListener('resize', this.onResize);
- },
+ }
componentWillUnmount() {
this._vid.current.removeEventListener('resize', this.onResize);
- },
+ }
- onResize: function(e) {
+ onResize = (e) => {
if (this.props.onResize) {
this.props.onResize(e);
}
- },
+ };
- render: function() {
+ render() {
return (
);
- },
-});
+ }
+}
diff --git a/src/components/views/voip/VideoView.js b/src/components/views/voip/VideoView.js
index a51ab70da9..374a12e82d 100644
--- a/src/components/views/voip/VideoView.js
+++ b/src/components/views/voip/VideoView.js
@@ -18,7 +18,6 @@ limitations under the License.
import React, {createRef} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
import classNames from 'classnames';
import * as sdk from '../../../index';
@@ -35,10 +34,8 @@ function getFullScreenElement() {
);
}
-export default createReactClass({
- displayName: 'VideoView',
-
- propTypes: {
+export default class VideoView extends React.Component {
+ static propTypes = {
// maxHeight style attribute for the video element
maxHeight: PropTypes.number,
@@ -48,27 +45,28 @@ export default createReactClass({
// a callback which is called when the video element is resized due to
// a change in video metadata
onResize: PropTypes.func,
- },
+ };
+
+ constructor(props) {
+ super(props);
- // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
- UNSAFE_componentWillMount: function() {
this._local = createRef();
this._remote = createRef();
- },
+ }
- componentDidMount: function() {
+ componentDidMount() {
this.dispatcherRef = dis.register(this.onAction);
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
dis.unregister(this.dispatcherRef);
- },
+ }
- getRemoteVideoElement: function() {
+ getRemoteVideoElement = () => {
return ReactDOM.findDOMNode(this._remote.current);
- },
+ };
- getRemoteAudioElement: function() {
+ getRemoteAudioElement = () => {
// this needs to be somewhere at the top of the DOM which
// always exists to avoid audio interruptions.
// Might as well just use DOM.
@@ -78,17 +76,17 @@ export default createReactClass({
+ "You need to add an to the DOM.");
}
return remoteAudioElement;
- },
+ };
- getLocalVideoElement: function() {
+ getLocalVideoElement = () => {
return ReactDOM.findDOMNode(this._local.current);
- },
+ };
- setContainer: function(c) {
+ setContainer = (c) => {
this.container = c;
- },
+ };
- onAction: function(payload) {
+ onAction = (payload) => {
switch (payload.action) {
case 'video_fullscreen': {
if (!this.container) {
@@ -117,9 +115,9 @@ export default createReactClass({
break;
}
}
- },
+ };
- render: function() {
+ render() {
const VideoFeed = sdk.getComponent('voip.VideoFeed');
// if we're fullscreen, we don't want to set a maxHeight on the video element.
@@ -140,5 +138,5 @@ export default createReactClass({
);
- },
-});
+ }
+}
diff --git a/src/hooks/useIsEncrypted.ts b/src/hooks/useIsEncrypted.ts
new file mode 100644
index 0000000000..79f3e539cd
--- /dev/null
+++ b/src/hooks/useIsEncrypted.ts
@@ -0,0 +1,36 @@
+/*
+Copyright 2020 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.
+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 {useCallback, useState} from "react";
+import {MatrixClient} from "matrix-js-sdk/src/client";
+import {MatrixEvent} from "matrix-js-sdk/src/models/event";
+import {Room} from "matrix-js-sdk/src/models/room";
+
+import {useEventEmitter} from "./useEventEmitter";
+
+// Hook to simplify watching whether a Matrix room is encrypted, returns undefined if room is undefined
+export function useIsEncrypted(cli: MatrixClient, room?: Room): boolean | undefined {
+ const [isEncrypted, setIsEncrypted] = useState(room ? cli.isRoomEncrypted(room.roomId) : undefined);
+
+ const update = useCallback((event: MatrixEvent) => {
+ if (room && event.getType() === "m.room.encryption") {
+ setIsEncrypted(cli.isRoomEncrypted(room.roomId));
+ }
+ }, [cli, room]);
+ useEventEmitter(room ? room.currentState : undefined, "RoomState.events", update);
+
+ return isEncrypted;
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 02382d4ff7..47063bdae4 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -62,11 +62,6 @@
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
"The server does not support the room version specified.": "The server does not support the room version specified.",
"Failure to create room": "Failure to create room",
- "Cancel entering passphrase?": "Cancel entering passphrase?",
- "Are you sure you want to cancel entering passphrase?": "Are you sure you want to cancel entering passphrase?",
- "Go Back": "Go Back",
- "Cancel": "Cancel",
- "Setting up keys": "Setting up keys",
"Sun": "Sun",
"Mon": "Mon",
"Tue": "Tue",
@@ -142,6 +137,11 @@
"Missing room_id in request": "Missing room_id in request",
"Room %(roomId)s not visible": "Room %(roomId)s not visible",
"Missing user_id in request": "Missing user_id in request",
+ "Cancel entering passphrase?": "Cancel entering passphrase?",
+ "Are you sure you want to cancel entering passphrase?": "Are you sure you want to cancel entering passphrase?",
+ "Go Back": "Go Back",
+ "Cancel": "Cancel",
+ "Setting up keys": "Setting up keys",
"Messages": "Messages",
"Actions": "Actions",
"Advanced": "Advanced",
@@ -149,6 +149,7 @@
"Command error": "Command error",
"Usage": "Usage",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message",
+ "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message",
"Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown",
"Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown",
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
@@ -684,6 +685,7 @@
"Public Name": "Public Name",
"Last seen": "Last seen",
"Failed to set display name": "Failed to set display name",
+ "Encryption": "Encryption",
"Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.": "Individually verify each session used by a user to mark it as trusted, not trusting cross-signed devices.",
"Securely cache encrypted messages locally for them to appear in search results, using ": "Securely cache encrypted messages locally for them to appear in search results, using ",
" to store messages from ": " to store messages from ",
@@ -907,10 +909,10 @@
"Message search": "Message search",
"Cross-signing": "Cross-signing",
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
- "Security & Privacy": "Security & Privacy",
"Where you’re logged in": "Where you’re logged in",
"Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Manage the names of and sign out of your sessions below or verify them in your User Profile.",
"A session's public name is visible to people you communicate with": "A session's public name is visible to people you communicate with",
+ "Privacy": "Privacy",
"%(brand)s collects anonymous analytics to allow us to improve the application.": "%(brand)s collects anonymous analytics to allow us to improve the application.",
"Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.",
"Learn more about how we use analytics.": "Learn more about how we use analytics.",
@@ -993,7 +995,7 @@
"Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)",
"Members only (since they were invited)": "Members only (since they were invited)",
"Members only (since they joined)": "Members only (since they joined)",
- "Encryption": "Encryption",
+ "Security & Privacy": "Security & Privacy",
"Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.",
"Encrypted": "Encrypted",
"Who can access this room?": "Who can access this room?",
@@ -1062,6 +1064,7 @@
"and %(count)s others...|other": "and %(count)s others...",
"and %(count)s others...|one": "and one other...",
"Invite to this room": "Invite to this room",
+ "Invite to this community": "Invite to this community",
"Invited": "Invited",
"Filter room members": "Filter room members",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)",
@@ -1423,7 +1426,6 @@
"Submit logs": "Submit logs",
"Failed to load group members": "Failed to load group members",
"Filter community members": "Filter community members",
- "Invite to this community": "Invite to this community",
"Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
"Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.",
"Failed to remove room from community": "Failed to remove room from community",
@@ -1684,6 +1686,8 @@
"Verification Requests": "Verification Requests",
"Toolbox": "Toolbox",
"Developer Tools": "Developer Tools",
+ "There was an error updating your community. The server is unable to process your request.": "There was an error updating your community. The server is unable to process your request.",
+ "Update community": "Update community",
"An error has occurred.": "An error has occurred.",
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
"Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.",
@@ -1706,9 +1710,11 @@
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s",
"Recent Conversations": "Recent Conversations",
"Suggestions": "Suggestions",
+ "May include members not in %(communityName)s": "May include members not in %(communityName)s",
"Recently Direct Messaged": "Recently Direct Messaged",
"Direct Messages": "Direct Messages",
"Start a conversation with someone using their name, username (like ) or email address.": "Start a conversation with someone using their name, username (like ) or email address.",
+ "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.": "Start a conversation with someone using their name, username (like ) or email address. This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here.",
"Go": "Go",
"Invite someone using their name, username (like ), email address or share this room.": "Invite someone using their name, username (like ), email address or share this room.",
"a new master key signature": "a new master key signature",
@@ -2107,14 +2113,18 @@
"Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
"Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s",
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
- "Switch to light mode": "Switch to light mode",
- "Switch to dark mode": "Switch to dark mode",
- "Switch theme": "Switch theme",
+ "Failed to find the general chat for this community": "Failed to find the general chat for this community",
"Notification settings": "Notification settings",
"Security & privacy": "Security & privacy",
"All settings": "All settings",
"Feedback": "Feedback",
+ "Community settings": "Community settings",
+ "User settings": "User settings",
+ "Switch to light mode": "Switch to light mode",
+ "Switch to dark mode": "Switch to dark mode",
+ "Switch theme": "Switch theme",
"User menu": "User menu",
+ "Community and user menu": "Community and user menu",
"Could not load user profile": "Could not load user profile",
"Verify this login": "Verify this login",
"Session verified": "Session verified",
diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts
index 448562b68a..dd60cde16d 100644
--- a/src/rageshake/submit-rageshake.ts
+++ b/src/rageshake/submit-rageshake.ts
@@ -90,32 +90,31 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) {
body.append('device_keys', keys.join(', '));
body.append('cross_signing_key', client.getCrossSigningId());
- body.append('device_keys', keys.join(', '));
-
// add cross-signing status information
const crossSigning = client._crypto._crossSigningInfo;
const secretStorage = client._crypto._secretStorage;
+ body.append("cross_signing_ready", String(await client.isCrossSigningReady()));
+ body.append("cross_signing_supported_by_hs",
+ String(await client.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")));
body.append("cross_signing_key", crossSigning.getId());
- body.append("cross_signing_pk_in_ssss",
+ body.append("cross_signing_pk_in_secret_storage",
String(!!(await crossSigning.isStoredInSecretStorage(secretStorage))));
- body.append("ssss_key_in_account", String(!!(await secretStorage.hasKey())));
const pkCache = client.getCrossSigningCacheCallbacks();
- body.append("master_pk_cached",
+ body.append("cross_signing_master_pk_cached",
String(!!(pkCache && await pkCache.getCrossSigningKeyCache("master"))));
- body.append("self_signing_pk_cached",
+ body.append("cross_signing_self_signing_pk_cached",
String(!!(pkCache && await pkCache.getCrossSigningKeyCache("self_signing"))));
- body.append("user_signing_pk_cached",
+ body.append("cross_signing_user_signing_pk_cached",
String(!!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing"))));
+ body.append("secret_storage_ready", String(await client.isSecretStorageReady()));
+ body.append("secret_storage_key_in_account", String(!!(await secretStorage.hasKey())));
+
const sessionBackupKeyFromCache = await client._crypto.getSessionBackupPrivateKey();
body.append("session_backup_key_cached", String(!!sessionBackupKeyFromCache));
body.append("session_backup_key_well_formed", String(sessionBackupKeyFromCache instanceof Uint8Array));
- body.append("cross_signing_supported_by_hs",
- String(await client.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")));
- body.append("cross_signing_ready", String(await client.isCrossSigningReady()));
- body.append("secret_storage_ready", String(await client.isSecretStorageReady()));
}
}
diff --git a/src/ratelimitedfunc.js b/src/ratelimitedfunc.js
index 1f15f11d91..3df3db615e 100644
--- a/src/ratelimitedfunc.js
+++ b/src/ratelimitedfunc.js
@@ -26,7 +26,7 @@ limitations under the License.
* on unmount or similar to cancel any pending update.
*/
-import { throttle } from "lodash";
+import {throttle} from "lodash";
export default function ratelimitedfunc(fn, time) {
const throttledFn = throttle(fn, time, {
diff --git a/src/stores/CommunityPrototypeStore.ts b/src/stores/CommunityPrototypeStore.ts
index 581f8a97c8..db747d105c 100644
--- a/src/stores/CommunityPrototypeStore.ts
+++ b/src/stores/CommunityPrototypeStore.ts
@@ -22,6 +22,11 @@ import { EffectiveMembership, getEffectiveMembership } from "../utils/membership
import SettingsStore from "../settings/SettingsStore";
import * as utils from "matrix-js-sdk/src/utils";
import { UPDATE_EVENT } from "./AsyncStore";
+import FlairStore from "./FlairStore";
+import TagOrderStore from "./TagOrderStore";
+import { MatrixClientPeg } from "../MatrixClientPeg";
+import GroupStore from "./GroupStore";
+import dis from "../dispatcher/dispatcher";
interface IState {
// nothing of value - we use account data
@@ -43,6 +48,46 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient {
return CommunityPrototypeStore.internalInstance;
}
+ public getSelectedCommunityId(): string {
+ if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
+ return TagOrderStore.getSelectedTags()[0];
+ }
+ return null; // no selection as far as this function is concerned
+ }
+
+ public getSelectedCommunityName(): string {
+ return CommunityPrototypeStore.instance.getCommunityName(this.getSelectedCommunityId());
+ }
+
+ public getSelectedCommunityGeneralChat(): Room {
+ const communityId = this.getSelectedCommunityId();
+ if (communityId) {
+ return this.getGeneralChat(communityId);
+ }
+ }
+
+ public getCommunityName(communityId: string): string {
+ const profile = FlairStore.getGroupProfileCachedFast(this.matrixClient, communityId);
+ return profile?.name || communityId;
+ }
+
+ public getCommunityProfile(communityId: string): { name?: string, avatarUrl?: string } {
+ return FlairStore.getGroupProfileCachedFast(this.matrixClient, communityId);
+ }
+
+ public getGeneralChat(communityId: string): Room {
+ const rooms = GroupStore.getGroupRooms(communityId)
+ .map(r => MatrixClientPeg.get().getRoom(r.roomId))
+ .filter(r => !!r);
+ let chat = rooms.find(r => {
+ const idState = r.currentState.getStateEvents("im.vector.general_chat", "");
+ if (!idState || idState.getContent()['groupId'] !== communityId) return false;
+ return true;
+ });
+ if (!chat) chat = rooms[0];
+ return chat; // can be null
+ }
+
protected async onAction(payload: ActionPayload): Promise {
if (!this.matrixClient || !SettingsStore.getValue("feature_communities_v2_prototypes")) {
return;
@@ -71,6 +116,15 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient {
if (payload.event_type.startsWith("im.vector.group_info.")) {
this.emit(UPDATE_EVENT, payload.event_type.substring("im.vector.group_info.".length));
}
+ } else if (payload.action === "select_tag") {
+ // Automatically select the general chat when switching communities
+ const chat = this.getGeneralChat(payload.tag);
+ if (chat) {
+ dis.dispatch({
+ action: 'view_room',
+ room_id: chat.roomId,
+ });
+ }
}
}
diff --git a/src/stores/FlairStore.js b/src/stores/FlairStore.js
index 94b81c1ba5..53d07d0452 100644
--- a/src/stores/FlairStore.js
+++ b/src/stores/FlairStore.js
@@ -148,6 +148,23 @@ class FlairStore extends EventEmitter {
});
}
+ /**
+ * Gets the profile for the given group if known, otherwise returns null.
+ * This triggers `getGroupProfileCached` if needed, though the result of the
+ * call will not be returned by this function.
+ * @param {MatrixClient} matrixClient The matrix client to use to fetch the profile, if needed.
+ * @param {string} groupId The group ID to get the profile for.
+ * @returns {*} The profile if known, otherwise null.
+ */
+ getGroupProfileCachedFast(matrixClient, groupId) {
+ if (!matrixClient || !groupId) return null;
+ if (this._groupProfiles[groupId]) {
+ return this._groupProfiles[groupId];
+ }
+ this.getGroupProfileCached(matrixClient, groupId);
+ return null;
+ }
+
async getGroupProfileCached(matrixClient, groupId) {
if (this._groupProfiles[groupId]) {
return this._groupProfiles[groupId];
diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts
index c1799978ad..34445d007b 100644
--- a/src/stores/RightPanelStore.ts
+++ b/src/stores/RightPanelStore.ts
@@ -223,3 +223,5 @@ export default class RightPanelStore extends Store {
return RightPanelStore.instance;
}
}
+
+window.mxRightPanelStore = RightPanelStore.getSharedInstance();
diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.js
index 63b8c428eb..981ce6eca9 100644
--- a/src/stores/SetupEncryptionStore.js
+++ b/src/stores/SetupEncryptionStore.js
@@ -16,7 +16,7 @@ limitations under the License.
import EventEmitter from 'events';
import { MatrixClientPeg } from '../MatrixClientPeg';
-import { accessSecretStorage, AccessCancelledError } from '../CrossSigningManager';
+import { accessSecretStorage, AccessCancelledError } from '../SecurityManager';
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
export const PHASE_INTRO = 0;
@@ -137,10 +137,10 @@ export class SetupEncryptionStore extends EventEmitter {
}
}
- _onUserTrustStatusChanged = async (userId) => {
+ _onUserTrustStatusChanged = (userId) => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
- const crossSigningReady = await MatrixClientPeg.get().isCrossSigningReady();
- if (crossSigningReady) {
+ const publicKeysTrusted = MatrixClientPeg.get().getCrossSigningId();
+ if (publicKeysTrusted) {
this.phase = PHASE_DONE;
this.emit("update");
}
@@ -150,7 +150,7 @@ export class SetupEncryptionStore extends EventEmitter {
this._setActiveVerificationRequest(request);
}
- onVerificationRequestChange = async () => {
+ onVerificationRequestChange = () => {
if (this.verificationRequest.cancelled) {
this.verificationRequest.off("change", this.onVerificationRequestChange);
this.verificationRequest = null;
@@ -161,8 +161,8 @@ export class SetupEncryptionStore extends EventEmitter {
// At this point, the verification has finished, we just need to wait for
// cross signing to be ready to use, so wait for the user trust status to
// change (or change to DONE if it's already ready).
- const crossSigningReady = await MatrixClientPeg.get().isCrossSigningReady();
- this.phase = crossSigningReady ? PHASE_DONE : PHASE_BUSY;
+ const publicKeysTrusted = MatrixClientPeg.get().getCrossSigningId();
+ this.phase = publicKeysTrusted ? PHASE_DONE : PHASE_BUSY;
this.emit("update");
}
}
diff --git a/src/stores/TagOrderStore.js b/src/stores/TagOrderStore.js
index 2eb35e6dc2..2b72a963b0 100644
--- a/src/stores/TagOrderStore.js
+++ b/src/stores/TagOrderStore.js
@@ -166,25 +166,6 @@ class TagOrderStore extends Store {
selectedTags: newTags,
});
- if (!allowMultiple && newTags.length === 1) {
- // We're in prototype behaviour: select the general chat for the community
- const rooms = GroupStore.getGroupRooms(newTags[0])
- .map(r => MatrixClientPeg.get().getRoom(r.roomId))
- .filter(r => !!r);
- let chat = rooms.find(r => {
- const idState = r.currentState.getStateEvents("im.vector.general_chat", "");
- if (!idState || idState.getContent()['groupId'] !== newTags[0]) return false;
- return true;
- });
- if (!chat) chat = rooms[0];
- if (chat) {
- dis.dispatch({
- action: 'view_room',
- room_id: chat.roomId,
- });
- }
- }
-
Analytics.trackEvent('FilterStore', 'select_tag');
}
break;
@@ -285,13 +266,6 @@ class TagOrderStore extends Store {
getSelectedTags() {
return this._state.selectedTags;
}
-
- getSelectedPrototypeTag() {
- if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
- return this.getSelectedTags()[0];
- }
- return null; // no selection as far as this function is concerned
- }
}
if (global.singletonTagOrderStore === undefined) {
diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts
index d35bbf1c88..9dbc4acafc 100644
--- a/src/toasts/SetupEncryptionToast.ts
+++ b/src/toasts/SetupEncryptionToast.ts
@@ -19,7 +19,7 @@ import * as sdk from "../index";
import { _t } from "../languageHandler";
import DeviceListener from "../DeviceListener";
import SetupEncryptionDialog from "../components/views/dialogs/SetupEncryptionDialog";
-import { accessSecretStorage } from "../CrossSigningManager";
+import { accessSecretStorage } from "../SecurityManager";
import ToastStore from "../stores/ToastStore";
import GenericToast from "../components/views/toasts/GenericToast";
diff --git a/src/utils/DMRoomMap.js b/src/utils/DMRoomMap.js
index 6ce92a0458..4e219b1611 100644
--- a/src/utils/DMRoomMap.js
+++ b/src/utils/DMRoomMap.js
@@ -16,7 +16,7 @@ limitations under the License.
*/
import {MatrixClientPeg} from '../MatrixClientPeg';
-import _uniq from 'lodash/uniq';
+import {uniq} from "lodash";
import {Room} from "matrix-js-sdk/src/matrix";
/**
@@ -111,7 +111,7 @@ export default class DMRoomMap {
userToRooms[userId] = [roomId];
} else {
roomIds.push(roomId);
- userToRooms[userId] = _uniq(roomIds);
+ userToRooms[userId] = uniq(roomIds);
}
});
return true;
diff --git a/src/verification.js b/src/verification.js
index 36fb8b0e4f..819370f239 100644
--- a/src/verification.js
+++ b/src/verification.js
@@ -21,7 +21,7 @@ import * as sdk from './index';
import { _t } from './languageHandler';
import {RightPanelPhases} from "./stores/RightPanelStorePhases";
import {findDMForUser} from './createRoom';
-import {accessSecretStorage} from './CrossSigningManager';
+import {accessSecretStorage} from './SecurityManager';
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
import {Action} from './dispatcher/actions';
diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js
index 3ac70bdac9..235ae94010 100644
--- a/test/components/structures/MessagePanel-test.js
+++ b/test/components/structures/MessagePanel-test.js
@@ -18,9 +18,7 @@ limitations under the License.
import SettingsStore from "../../../src/settings/SettingsStore";
import React from 'react';
-import createReactClass from 'create-react-class';
import ReactDOM from "react-dom";
-import PropTypes from "prop-types";
const TestUtils = require('react-dom/test-utils');
const expect = require('expect');
import { EventEmitter } from "events";
@@ -47,21 +45,19 @@ let client;
const room = new Matrix.Room();
// wrap MessagePanel with a component which provides the MatrixClient in the context.
-const WrappedMessagePanel = createReactClass({
- getInitialState: function() {
- return {
- resizeNotifier: new EventEmitter(),
- };
- },
+class WrappedMessagePanel extends React.Component {
+ state = {
+ resizeNotifier: new EventEmitter(),
+ };
- render: function() {
+ render() {
return ;
- },
-});
+ }
+}
describe('MessagePanel', function() {
const clock = mockclock.clock();
@@ -214,7 +210,7 @@ describe('MessagePanel', function() {
room: roomId,
user: alice,
content: {
- "join_rule": "invite"
+ "join_rule": "invite",
},
ts: ts0 + 2,
}),
diff --git a/test/components/stub-component.js b/test/components/stub-component.js
index a5c3b44409..e242c06707 100644
--- a/test/components/stub-component.js
+++ b/test/components/stub-component.js
@@ -2,19 +2,19 @@
*/
import React from 'react';
-import createReactClass from 'create-react-class';
-export default function(opts) {
- opts = opts || {};
- if (!opts.displayName) {
- opts.displayName = 'StubComponent';
- }
-
- if (!opts.render) {
- opts.render = function() {
- return