Remove create-react-class

This commit is contained in:
Michael Telatynski 2020-08-29 12:14:16 +01:00
parent 672d0fe97b
commit 72498df28f
108 changed files with 3059 additions and 3545 deletions

View file

@ -61,7 +61,6 @@
"classnames": "^2.2.6", "classnames": "^2.2.6",
"commonmark": "^0.29.1", "commonmark": "^0.29.1",
"counterpart": "^0.18.6", "counterpart": "^0.18.6",
"create-react-class": "^15.6.3",
"diff-dom": "^4.1.6", "diff-dom": "^4.1.6",
"diff-match-patch": "^1.0.5", "diff-match-patch": "^1.0.5",
"emojibase-data": "^5.0.1", "emojibase-data": "^5.0.1",

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import createReactClass from 'create-react-class'; import React from "react";
import * as sdk from './index'; import * as sdk from './index';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
@ -24,21 +24,19 @@ import { _t } from './languageHandler';
* Wrap an asynchronous loader function with a react component which shows a * Wrap an asynchronous loader function with a react component which shows a
* spinner until the real component loads. * spinner until the real component loads.
*/ */
export default createReactClass({ export default class AsyncWrapper extends React.Component {
propTypes: { static propTypes = {
/** A promise which resolves with the real component /** A promise which resolves with the real component
*/ */
prom: PropTypes.object.isRequired, prom: PropTypes.object.isRequired,
}, };
getInitialState: function() { state = {
return { component: null,
component: null, error: null,
error: null, };
};
},
componentDidMount: function() { componentDidMount() {
this._unmounted = false; this._unmounted = false;
// XXX: temporary logging to try to diagnose // XXX: temporary logging to try to diagnose
// https://github.com/vector-im/element-web/issues/3148 // https://github.com/vector-im/element-web/issues/3148
@ -56,17 +54,17 @@ export default createReactClass({
console.warn('AsyncWrapper promise failed', e); console.warn('AsyncWrapper promise failed', e);
this.setState({error: e}); this.setState({error: e});
}); });
}, }
componentWillUnmount: function() { componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
}, }
_onWrapperCancelClick: function() { _onWrapperCancelClick = () => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
render: function() { render() {
if (this.state.component) { if (this.state.component) {
const Component = this.state.component; const Component = this.state.component;
return <Component {...this.props} />; return <Component {...this.props} />;
@ -87,6 +85,6 @@ export default createReactClass({
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />; return <Spinner />;
} }
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import FileSaver from 'file-saver'; import FileSaver from 'file-saver';
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { MatrixClient } from 'matrix-js-sdk'; import { MatrixClient } from 'matrix-js-sdk';
@ -27,34 +26,31 @@ import * as sdk from '../../../index';
const PHASE_EDIT = 1; const PHASE_EDIT = 1;
const PHASE_EXPORTING = 2; const PHASE_EXPORTING = 2;
export default createReactClass({ export default class ExportE2eKeysDialog extends React.Component {
displayName: 'ExportE2eKeysDialog', static propTypes = {
propTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
getInitialState: function() { constructor(props) {
return { super(props);
phase: PHASE_EDIT,
errStr: null,
};
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._unmounted = false; this._unmounted = false;
this._passphrase1 = createRef(); this._passphrase1 = createRef();
this._passphrase2 = createRef(); this._passphrase2 = createRef();
},
componentWillUnmount: function() { this.state = {
phase: PHASE_EDIT,
errStr: null,
};
}
componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
}, }
_onPassphraseFormSubmit: function(ev) { _onPassphraseFormSubmit = (ev) => {
ev.preventDefault(); ev.preventDefault();
const passphrase = this._passphrase1.current.value; const passphrase = this._passphrase1.current.value;
@ -69,9 +65,9 @@ export default createReactClass({
this._startExport(passphrase); this._startExport(passphrase);
return false; return false;
}, };
_startExport: function(passphrase) { _startExport(passphrase) {
// extra Promise.resolve() to turn synchronous exceptions into // extra Promise.resolve() to turn synchronous exceptions into
// asynchronous ones. // asynchronous ones.
Promise.resolve().then(() => { Promise.resolve().then(() => {
@ -102,15 +98,15 @@ export default createReactClass({
errStr: null, errStr: null,
phase: PHASE_EXPORTING, phase: PHASE_EXPORTING,
}); });
}, }
_onCancelClick: function(ev) { _onCancelClick = (ev) => {
ev.preventDefault(); ev.preventDefault();
this.props.onFinished(false); this.props.onFinished(false);
return false; return false;
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const disableForm = (this.state.phase === PHASE_EXPORTING); const disableForm = (this.state.phase === PHASE_EXPORTING);
@ -184,5 +180,5 @@ export default createReactClass({
</form> </form>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -16,7 +16,6 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { MatrixClient } from 'matrix-js-sdk'; import { MatrixClient } from 'matrix-js-sdk';
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
@ -38,48 +37,45 @@ function readFileAsArrayBuffer(file) {
const PHASE_EDIT = 1; const PHASE_EDIT = 1;
const PHASE_IMPORTING = 2; const PHASE_IMPORTING = 2;
export default createReactClass({ export default class ImportE2eKeysDialog extends React.Component {
displayName: 'ImportE2eKeysDialog', static propTypes = {
propTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
getInitialState: function() { constructor(props) {
return { super(props);
enableSubmit: false,
phase: PHASE_EDIT,
errStr: null,
};
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._unmounted = false; this._unmounted = false;
this._file = createRef(); this._file = createRef();
this._passphrase = createRef(); this._passphrase = createRef();
},
componentWillUnmount: function() { this.state = {
enableSubmit: false,
phase: PHASE_EDIT,
errStr: null,
};
}
componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
}, }
_onFormChange: function(ev) { _onFormChange = (ev) => {
const files = this._file.current.files || []; const files = this._file.current.files || [];
this.setState({ this.setState({
enableSubmit: (this._passphrase.current.value !== "" && files.length > 0), enableSubmit: (this._passphrase.current.value !== "" && files.length > 0),
}); });
}, };
_onFormSubmit: function(ev) { _onFormSubmit = (ev) => {
ev.preventDefault(); ev.preventDefault();
this._startImport(this._file.current.files[0], this._passphrase.current.value); this._startImport(this._file.current.files[0], this._passphrase.current.value);
return false; return false;
}, };
_startImport: function(file, passphrase) { _startImport(file, passphrase) {
this.setState({ this.setState({
errStr: null, errStr: null,
phase: PHASE_IMPORTING, phase: PHASE_IMPORTING,
@ -105,15 +101,15 @@ export default createReactClass({
phase: PHASE_EDIT, phase: PHASE_EDIT,
}); });
}); });
}, }
_onCancelClick: function(ev) { _onCancelClick = (ev) => {
ev.preventDefault(); ev.preventDefault();
this.props.onFinished(false); this.props.onFinished(false);
return false; return false;
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const disableForm = (this.state.phase !== PHASE_EDIT); const disableForm = (this.state.phase !== PHASE_EDIT);
@ -188,5 +184,5 @@ export default createReactClass({
</form> </form>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {Filter} from 'matrix-js-sdk'; import {Filter} from 'matrix-js-sdk';
@ -28,23 +27,20 @@ import { _t } from '../../languageHandler';
/* /*
* Component which shows the filtered file using a TimelinePanel * Component which shows the filtered file using a TimelinePanel
*/ */
const FilePanel = createReactClass({ class FilePanel extends React.Component {
displayName: 'FilePanel', static propTypes = {
roomId: PropTypes.string.isRequired,
};
// This is used to track if a decrypted event was a live event and should be // This is used to track if a decrypted event was a live event and should be
// added to the timeline. // added to the timeline.
decryptingEvents: new Set(), decryptingEvents = new Set();
propTypes: { state = {
roomId: PropTypes.string.isRequired, timelineSet: null,
}, };
getInitialState: function() { onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => {
return {
timelineSet: null,
};
},
onRoomTimeline(ev, room, toStartOfTimeline, removed, data) {
if (room.roomId !== this.props.roomId) return; if (room.roomId !== this.props.roomId) return;
if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return;
@ -53,9 +49,9 @@ const FilePanel = createReactClass({
} else { } else {
this.addEncryptedLiveEvent(ev); this.addEncryptedLiveEvent(ev);
} }
}, };
onEventDecrypted(ev, err) { onEventDecrypted = (ev, err) => {
if (ev.getRoomId() !== this.props.roomId) return; if (ev.getRoomId() !== this.props.roomId) return;
const eventId = ev.getId(); const eventId = ev.getId();
@ -63,7 +59,7 @@ const FilePanel = createReactClass({
if (err) return; if (err) return;
this.addEncryptedLiveEvent(ev); this.addEncryptedLiveEvent(ev);
}, };
addEncryptedLiveEvent(ev, toStartOfTimeline) { addEncryptedLiveEvent(ev, toStartOfTimeline) {
if (!this.state.timelineSet) return; if (!this.state.timelineSet) return;
@ -77,7 +73,7 @@ const FilePanel = createReactClass({
if (!this.state.timelineSet.eventIdToTimeline(ev.getId())) { if (!this.state.timelineSet.eventIdToTimeline(ev.getId())) {
this.state.timelineSet.addEventToTimeline(ev, timeline, false); this.state.timelineSet.addEventToTimeline(ev, timeline, false);
} }
}, }
async componentDidMount() { async componentDidMount() {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
@ -98,7 +94,7 @@ const FilePanel = createReactClass({
client.on('Room.timeline', this.onRoomTimeline); client.on('Room.timeline', this.onRoomTimeline);
client.on('Event.decrypted', this.onEventDecrypted); client.on('Event.decrypted', this.onEventDecrypted);
} }
}, }
componentWillUnmount() { componentWillUnmount() {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
@ -110,7 +106,7 @@ const FilePanel = createReactClass({
client.removeListener('Room.timeline', this.onRoomTimeline); client.removeListener('Room.timeline', this.onRoomTimeline);
client.removeListener('Event.decrypted', this.onEventDecrypted); client.removeListener('Event.decrypted', this.onEventDecrypted);
} }
}, }
async fetchFileEventsServer(room) { async fetchFileEventsServer(room) {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
@ -134,9 +130,9 @@ const FilePanel = createReactClass({
const timelineSet = room.getOrCreateFilteredTimelineSet(filter); const timelineSet = room.getOrCreateFilteredTimelineSet(filter);
return timelineSet; return timelineSet;
}, }
onPaginationRequest(timelineWindow, direction, limit) { onPaginationRequest = (timelineWindow, direction, limit) => {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const eventIndex = EventIndexPeg.get(); const eventIndex = EventIndexPeg.get();
const roomId = this.props.roomId; const roomId = this.props.roomId;
@ -152,7 +148,7 @@ const FilePanel = createReactClass({
} else { } else {
return timelineWindow.paginate(direction, limit); return timelineWindow.paginate(direction, limit);
} }
}, };
async updateTimelineSet(roomId: string) { async updateTimelineSet(roomId: string) {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
@ -188,9 +184,9 @@ const FilePanel = createReactClass({
} else { } else {
console.error("Failed to add filtered timelineSet for FilePanel as no room!"); console.error("Failed to add filtered timelineSet for FilePanel as no room!");
} }
}, }
render: function() { render() {
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
return <div className="mx_FilePanel mx_RoomView_messageListWrapper"> return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
<div className="mx_RoomView_empty"> <div className="mx_RoomView_empty">
@ -239,7 +235,7 @@ const FilePanel = createReactClass({
</div> </div>
); );
} }
}, }
}); }
export default FilePanel; export default FilePanel;

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {MatrixClientPeg} from '../../MatrixClientPeg'; import {MatrixClientPeg} from '../../MatrixClientPeg';
import * as sdk from '../../index'; import * as sdk from '../../index';
@ -70,10 +69,8 @@ const UserSummaryType = PropTypes.shape({
}).isRequired, }).isRequired,
}); });
const CategoryRoomList = createReactClass({ class CategoryRoomList extends React.Component {
displayName: 'CategoryRoomList', static propTypes = {
props: {
rooms: PropTypes.arrayOf(RoomSummaryType).isRequired, rooms: PropTypes.arrayOf(RoomSummaryType).isRequired,
category: PropTypes.shape({ category: PropTypes.shape({
profile: PropTypes.shape({ profile: PropTypes.shape({
@ -84,9 +81,9 @@ const CategoryRoomList = createReactClass({
// Whether the list should be editable // Whether the list should be editable
editing: PropTypes.bool.isRequired, editing: PropTypes.bool.isRequired,
}, };
onAddRoomsToSummaryClicked: function(ev) { onAddRoomsToSummaryClicked = (ev) => {
ev.preventDefault(); ev.preventDefault();
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, { Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, {
@ -122,9 +119,9 @@ const CategoryRoomList = createReactClass({
}); });
}, },
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
}, };
render: function() { render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ? const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" (<AccessibleButton className="mx_GroupView_featuredThings_addButton"
@ -155,19 +152,17 @@ const CategoryRoomList = createReactClass({
{ roomNodes } { roomNodes }
{ addButton } { addButton }
</div>; </div>;
}, }
}); }
const FeaturedRoom = createReactClass({ class FeaturedRoom extends React.Component {
displayName: 'FeaturedRoom', static propTypes = {
props: {
summaryInfo: RoomSummaryType.isRequired, summaryInfo: RoomSummaryType.isRequired,
editing: PropTypes.bool.isRequired, editing: PropTypes.bool.isRequired,
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
}, };
onClick: function(e) { onClick = (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -176,9 +171,9 @@ const FeaturedRoom = createReactClass({
room_alias: this.props.summaryInfo.profile.canonical_alias, room_alias: this.props.summaryInfo.profile.canonical_alias,
room_id: this.props.summaryInfo.room_id, room_id: this.props.summaryInfo.room_id,
}); });
}, };
onDeleteClicked: function(e) { onDeleteClicked = (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
GroupStore.removeRoomFromGroupSummary( GroupStore.removeRoomFromGroupSummary(
@ -201,9 +196,9 @@ const FeaturedRoom = createReactClass({
description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}), description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}),
}); });
}); });
}, };
render: function() { render() {
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
const roomName = this.props.summaryInfo.profile.name || const roomName = this.props.summaryInfo.profile.name ||
@ -243,13 +238,11 @@ const FeaturedRoom = createReactClass({
<div className="mx_GroupView_featuredThing_name">{ roomNameNode }</div> <div className="mx_GroupView_featuredThing_name">{ roomNameNode }</div>
{ deleteButton } { deleteButton }
</AccessibleButton>; </AccessibleButton>;
}, }
}); }
const RoleUserList = createReactClass({ class RoleUserList extends React.Component {
displayName: 'RoleUserList', static propTypes = {
props: {
users: PropTypes.arrayOf(UserSummaryType).isRequired, users: PropTypes.arrayOf(UserSummaryType).isRequired,
role: PropTypes.shape({ role: PropTypes.shape({
profile: PropTypes.shape({ profile: PropTypes.shape({
@ -260,9 +253,9 @@ const RoleUserList = createReactClass({
// Whether the list should be editable // Whether the list should be editable
editing: PropTypes.bool.isRequired, editing: PropTypes.bool.isRequired,
}, };
onAddUsersClicked: function(ev) { onAddUsersClicked = (ev) => {
ev.preventDefault(); ev.preventDefault();
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, { Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, {
@ -298,9 +291,9 @@ const RoleUserList = createReactClass({
}); });
}, },
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
}, };
render: function() { render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ? const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}> (<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
@ -325,19 +318,17 @@ const RoleUserList = createReactClass({
{ userNodes } { userNodes }
{ addButton } { addButton }
</div>; </div>;
}, }
}); }
const FeaturedUser = createReactClass({ class FeaturedUser extends React.Component {
displayName: 'FeaturedUser', static propTypes = {
props: {
summaryInfo: UserSummaryType.isRequired, summaryInfo: UserSummaryType.isRequired,
editing: PropTypes.bool.isRequired, editing: PropTypes.bool.isRequired,
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
}, };
onClick: function(e) { onClick = (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -345,9 +336,9 @@ const FeaturedUser = createReactClass({
action: 'view_start_chat_or_reuse', action: 'view_start_chat_or_reuse',
user_id: this.props.summaryInfo.user_id, user_id: this.props.summaryInfo.user_id,
}); });
}, };
onDeleteClicked: function(e) { onDeleteClicked = (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
GroupStore.removeUserFromGroupSummary( GroupStore.removeUserFromGroupSummary(
@ -368,9 +359,9 @@ const FeaturedUser = createReactClass({
description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}), description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}),
}); });
}); });
}, };
render: function() { render() {
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id; const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;
@ -394,41 +385,37 @@ const FeaturedUser = createReactClass({
<div className="mx_GroupView_featuredThing_name">{ userNameNode }</div> <div className="mx_GroupView_featuredThing_name">{ userNameNode }</div>
{ deleteButton } { deleteButton }
</AccessibleButton>; </AccessibleButton>;
}, }
}); }
const GROUP_JOINPOLICY_OPEN = "open"; const GROUP_JOINPOLICY_OPEN = "open";
const GROUP_JOINPOLICY_INVITE = "invite"; const GROUP_JOINPOLICY_INVITE = "invite";
export default createReactClass({ export default class GroupView extends React.Component {
displayName: 'GroupView', static propTypes = {
propTypes: {
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
// Whether this is the first time the group admin is viewing the group // Whether this is the first time the group admin is viewing the group
groupIsNew: PropTypes.bool, groupIsNew: PropTypes.bool,
}, };
getInitialState: function() { state = {
return { summary: null,
summary: null, isGroupPublicised: null,
isGroupPublicised: null, isUserPrivileged: null,
isUserPrivileged: null, groupRooms: null,
groupRooms: null, groupRoomsLoading: null,
groupRoomsLoading: null, error: null,
error: null, editing: false,
editing: false, saving: false,
saving: false, uploadingAvatar: false,
uploadingAvatar: false, avatarChanged: false,
avatarChanged: false, membershipBusy: false,
membershipBusy: false, publicityBusy: false,
publicityBusy: false, inviterProfile: null,
inviterProfile: null, showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup,
showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, };
};
},
componentDidMount: function() { componentDidMount() {
this._unmounted = false; this._unmounted = false;
this._matrixClient = MatrixClientPeg.get(); this._matrixClient = MatrixClientPeg.get();
this._matrixClient.on("Group.myMembership", this._onGroupMyMembership); this._matrixClient.on("Group.myMembership", this._onGroupMyMembership);
@ -437,9 +424,9 @@ export default createReactClass({
this._dispatcherRef = dis.register(this._onAction); this._dispatcherRef = dis.register(this._onAction);
this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate);
}, }
componentWillUnmount: function() { componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership); this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
dis.unregister(this._dispatcherRef); dis.unregister(this._dispatcherRef);
@ -448,10 +435,11 @@ export default createReactClass({
if (this._rightPanelStoreToken) { if (this._rightPanelStoreToken) {
this._rightPanelStoreToken.remove(); this._rightPanelStoreToken.remove();
} }
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(newProps) { // eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(newProps) {
if (this.props.groupId !== newProps.groupId) { if (this.props.groupId !== newProps.groupId) {
this.setState({ this.setState({
summary: null, summary: null,
@ -460,24 +448,24 @@ export default createReactClass({
this._initGroupStore(newProps.groupId); this._initGroupStore(newProps.groupId);
}); });
} }
}, }
_onRightPanelStoreUpdate: function() { _onRightPanelStoreUpdate = () => {
this.setState({ this.setState({
showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup,
}); });
}, };
_onGroupMyMembership: function(group) { _onGroupMyMembership = (group) => {
if (this._unmounted || group.groupId !== this.props.groupId) return; if (this._unmounted || group.groupId !== this.props.groupId) return;
if (group.myMembership === 'leave') { if (group.myMembership === 'leave') {
// Leave settings - the user might have clicked the "Leave" button // Leave settings - the user might have clicked the "Leave" button
this._closeSettings(); this._closeSettings();
} }
this.setState({membershipBusy: false}); this.setState({membershipBusy: false});
}, };
_initGroupStore: function(groupId, firstInit) { _initGroupStore(groupId, firstInit) {
const group = this._matrixClient.getGroup(groupId); const group = this._matrixClient.getGroup(groupId);
if (group && group.inviter && group.inviter.userId) { if (group && group.inviter && group.inviter.userId) {
this._fetchInviterProfile(group.inviter.userId); this._fetchInviterProfile(group.inviter.userId);
@ -506,9 +494,9 @@ export default createReactClass({
}); });
} }
}); });
}, }
onGroupStoreUpdated(firstInit) { onGroupStoreUpdated = (firstInit) => {
if (this._unmounted) return; if (this._unmounted) return;
const summary = GroupStore.getSummary(this.props.groupId); const summary = GroupStore.getSummary(this.props.groupId);
if (summary.profile) { if (summary.profile) {
@ -533,7 +521,7 @@ export default createReactClass({
if (this.props.groupIsNew && firstInit) { if (this.props.groupIsNew && firstInit) {
this._onEditClick(); this._onEditClick();
} }
}, };
_fetchInviterProfile(userId) { _fetchInviterProfile(userId) {
this.setState({ this.setState({
@ -555,9 +543,9 @@ export default createReactClass({
inviterProfileBusy: false, inviterProfileBusy: false,
}); });
}); });
}, }
_onEditClick: function() { _onEditClick = () => {
this.setState({ this.setState({
editing: true, editing: true,
profileForm: Object.assign({}, this.state.summary.profile), profileForm: Object.assign({}, this.state.summary.profile),
@ -568,20 +556,20 @@ export default createReactClass({
GROUP_JOINPOLICY_INVITE, GROUP_JOINPOLICY_INVITE,
}, },
}); });
}, };
_onShareClick: function() { _onShareClick = () => {
const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share community dialog', '', ShareDialog, { Modal.createTrackedDialog('share community dialog', '', ShareDialog, {
target: this._matrixClient.getGroup(this.props.groupId) || new Group(this.props.groupId), target: this._matrixClient.getGroup(this.props.groupId) || new Group(this.props.groupId),
}); });
}, };
_onCancelClick: function() { _onCancelClick = () => {
this._closeSettings(); this._closeSettings();
}, };
_onAction(payload) { _onAction = (payload) => {
switch (payload.action) { switch (payload.action) {
// NOTE: close_settings is an app-wide dispatch; as it is dispatched from MatrixChat // NOTE: close_settings is an app-wide dispatch; as it is dispatched from MatrixChat
case 'close_settings': case 'close_settings':
@ -593,34 +581,34 @@ export default createReactClass({
default: default:
break; break;
} }
}, };
_closeSettings() { _closeSettings = () => {
dis.dispatch({action: 'close_settings'}); dis.dispatch({action: 'close_settings'});
}, };
_onNameChange: function(value) { _onNameChange = (value) => {
const newProfileForm = Object.assign(this.state.profileForm, { name: value }); const newProfileForm = Object.assign(this.state.profileForm, { name: value });
this.setState({ this.setState({
profileForm: newProfileForm, profileForm: newProfileForm,
}); });
}, };
_onShortDescChange: function(value) { _onShortDescChange = (value) => {
const newProfileForm = Object.assign(this.state.profileForm, { short_description: value }); const newProfileForm = Object.assign(this.state.profileForm, { short_description: value });
this.setState({ this.setState({
profileForm: newProfileForm, profileForm: newProfileForm,
}); });
}, };
_onLongDescChange: function(e) { _onLongDescChange = (e) => {
const newProfileForm = Object.assign(this.state.profileForm, { long_description: e.target.value }); const newProfileForm = Object.assign(this.state.profileForm, { long_description: e.target.value });
this.setState({ this.setState({
profileForm: newProfileForm, profileForm: newProfileForm,
}); });
}, };
_onAvatarSelected: function(ev) { _onAvatarSelected = ev => {
const file = ev.target.files[0]; const file = ev.target.files[0];
if (!file) return; if (!file) return;
@ -644,15 +632,15 @@ export default createReactClass({
description: _t('Failed to upload image'), description: _t('Failed to upload image'),
}); });
}); });
}, };
_onJoinableChange: function(ev) { _onJoinableChange = ev => {
this.setState({ this.setState({
joinableForm: { policyType: ev.target.value }, joinableForm: { policyType: ev.target.value },
}); });
}, };
_onSaveClick: function() { _onSaveClick = () => {
this.setState({saving: true}); this.setState({saving: true});
const savePromise = this.state.isUserPrivileged ? this._saveGroup() : Promise.resolve(); const savePromise = this.state.isUserPrivileged ? this._saveGroup() : Promise.resolve();
savePromise.then((result) => { savePromise.then((result) => {
@ -683,16 +671,16 @@ export default createReactClass({
avatarChanged: false, avatarChanged: false,
}); });
}); });
}, };
_saveGroup: async function() { async _saveGroup() {
await this._matrixClient.setGroupProfile(this.props.groupId, this.state.profileForm); await this._matrixClient.setGroupProfile(this.props.groupId, this.state.profileForm);
await this._matrixClient.setGroupJoinPolicy(this.props.groupId, { await this._matrixClient.setGroupJoinPolicy(this.props.groupId, {
type: this.state.joinableForm.policyType, type: this.state.joinableForm.policyType,
}); });
}, }
_onAcceptInviteClick: async function() { _onAcceptInviteClick = async () => {
this.setState({membershipBusy: true}); this.setState({membershipBusy: true});
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the // Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
@ -709,9 +697,9 @@ export default createReactClass({
description: _t("Unable to accept invite"), description: _t("Unable to accept invite"),
}); });
}); });
}, };
_onRejectInviteClick: async function() { _onRejectInviteClick = async () => {
this.setState({membershipBusy: true}); this.setState({membershipBusy: true});
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the // Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
@ -728,9 +716,9 @@ export default createReactClass({
description: _t("Unable to reject invite"), description: _t("Unable to reject invite"),
}); });
}); });
}, };
_onJoinClick: async function() { _onJoinClick = async () => {
if (this._matrixClient.isGuest()) { if (this._matrixClient.isGuest()) {
dis.dispatch({action: 'require_registration', screen_after: {screen: `group/${this.props.groupId}`}}); dis.dispatch({action: 'require_registration', screen_after: {screen: `group/${this.props.groupId}`}});
return; return;
@ -752,9 +740,9 @@ export default createReactClass({
description: _t("Unable to join community"), description: _t("Unable to join community"),
}); });
}); });
}, };
_leaveGroupWarnings: function() { _leaveGroupWarnings() {
const warnings = []; const warnings = [];
if (this.state.isUserPrivileged) { if (this.state.isUserPrivileged) {
@ -768,10 +756,9 @@ export default createReactClass({
} }
return warnings; return warnings;
}, }
_onLeaveClick = () => {
_onLeaveClick: function() {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const warnings = this._leaveGroupWarnings(); const warnings = this._leaveGroupWarnings();
@ -806,13 +793,13 @@ export default createReactClass({
}); });
}, },
}); });
}, };
_onAddRoomsClick: function() { _onAddRoomsClick = () => {
showGroupAddRoomDialog(this.props.groupId); showGroupAddRoomDialog(this.props.groupId);
}, };
_getGroupSection: function() { _getGroupSection() {
const groupSettingsSectionClasses = classnames({ const groupSettingsSectionClasses = classnames({
"mx_GroupView_group": this.state.editing, "mx_GroupView_group": this.state.editing,
"mx_GroupView_group_disabled": this.state.editing && !this.state.isUserPrivileged, "mx_GroupView_group_disabled": this.state.editing && !this.state.isUserPrivileged,
@ -856,9 +843,9 @@ export default createReactClass({
{ this._getLongDescriptionNode() } { this._getLongDescriptionNode() }
{ this._getRoomsNode() } { this._getRoomsNode() }
</div>; </div>;
}, }
_getRoomsNode: function() { _getRoomsNode() {
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList'); const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const TintableSvg = sdk.getComponent('elements.TintableSvg'); const TintableSvg = sdk.getComponent('elements.TintableSvg');
@ -902,9 +889,9 @@ export default createReactClass({
className={roomDetailListClassName} /> className={roomDetailListClassName} />
} }
</div>; </div>;
}, }
_getFeaturedRoomsNode: function() { _getFeaturedRoomsNode() {
const summary = this.state.summary; const summary = this.state.summary;
const defaultCategoryRooms = []; const defaultCategoryRooms = [];
@ -943,9 +930,9 @@ export default createReactClass({
{ defaultCategoryNode } { defaultCategoryNode }
{ categoryRoomNodes } { categoryRoomNodes }
</div>; </div>;
}, }
_getFeaturedUsersNode: function() { _getFeaturedUsersNode() {
const summary = this.state.summary; const summary = this.state.summary;
const noRoleUsers = []; const noRoleUsers = [];
@ -984,9 +971,9 @@ export default createReactClass({
{ noRoleNode } { noRoleNode }
{ roleUserNodes } { roleUserNodes }
</div>; </div>;
}, }
_getMembershipSection: function() { _getMembershipSection() {
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -1100,9 +1087,9 @@ export default createReactClass({
</div> </div>
</div> </div>
</div>; </div>;
}, }
_getJoinableNode: function() { _getJoinableNode() {
const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
return this.state.editing ? <div> return this.state.editing ? <div>
<h3> <h3>
@ -1136,9 +1123,9 @@ export default createReactClass({
</label> </label>
</div> </div>
</div> : null; </div> : null;
}, }
_getLongDescriptionNode: function() { _getLongDescriptionNode() {
const summary = this.state.summary; const summary = this.state.summary;
let description = null; let description = null;
if (summary.profile && summary.profile.long_description) { if (summary.profile && summary.profile.long_description) {
@ -1175,9 +1162,9 @@ export default createReactClass({
<div className="mx_GroupView_groupDesc"> <div className="mx_GroupView_groupDesc">
{ description } { description }
</div>; </div>;
}, }
render: function() { render() {
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar"); const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
@ -1366,5 +1353,5 @@ export default createReactClass({
console.error("Invalid state for GroupView"); console.error("Invalid state for GroupView");
return <div />; return <div />;
} }
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import {InteractiveAuth} from "matrix-js-sdk"; import {InteractiveAuth} from "matrix-js-sdk";
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents'; 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 const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
export default createReactClass({ export default class InteractiveAuthComponent extends React.Component {
displayName: 'InteractiveAuth', static propTypes = {
propTypes: {
// matrix client to use for UI auth requests // matrix client to use for UI auth requests
matrixClient: PropTypes.object.isRequired, matrixClient: PropTypes.object.isRequired,
@ -86,20 +83,19 @@ export default createReactClass({
// continueText and continueKind are passed straight through to the AuthEntryComponent. // continueText and continueKind are passed straight through to the AuthEntryComponent.
continueText: PropTypes.string, continueText: PropTypes.string,
continueKind: PropTypes.string, continueKind: PropTypes.string,
}, };
getInitialState: function() { constructor(props) {
return { super(props);
this.state = {
authStage: null, authStage: null,
busy: false, busy: false,
errorText: null, errorText: null,
stageErrorText: null, stageErrorText: null,
submitButtonEnabled: false, submitButtonEnabled: false,
}; };
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._unmounted = false; this._unmounted = false;
this._authLogic = new InteractiveAuth({ this._authLogic = new InteractiveAuth({
authData: this.props.authData, authData: this.props.authData,
@ -141,17 +137,17 @@ export default createReactClass({
} }
this._stageComponent = createRef(); this._stageComponent = createRef();
}, }
componentWillUnmount: function() { componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
if (this._intervalId !== null) { if (this._intervalId !== null) {
clearInterval(this._intervalId); clearInterval(this._intervalId);
} }
}, }
_requestEmailToken: async function(...args) { _requestEmailToken = async (...args) => {
this.setState({ this.setState({
busy: true, busy: true,
}); });
@ -162,15 +158,15 @@ export default createReactClass({
busy: false, busy: false,
}); });
} }
}, };
tryContinue: function() { tryContinue = () => {
if (this._stageComponent.current && this._stageComponent.current.tryContinue) { if (this._stageComponent.current && this._stageComponent.current.tryContinue) {
this._stageComponent.current.tryContinue(); this._stageComponent.current.tryContinue();
} }
}, };
_authStateUpdated: function(stageType, stageState) { _authStateUpdated = (stageType, stageState) => {
const oldStage = this.state.authStage; const oldStage = this.state.authStage;
this.setState({ this.setState({
busy: false, busy: false,
@ -180,16 +176,16 @@ export default createReactClass({
}, () => { }, () => {
if (oldStage != stageType) this._setFocus(); if (oldStage != stageType) this._setFocus();
}); });
}, };
_requestCallback: function(auth) { _requestCallback = (auth) => {
// This wrapper just exists because the js-sdk passes a second // This wrapper just exists because the js-sdk passes a second
// 'busy' param for backwards compat. This throws the tests off // 'busy' param for backwards compat. This throws the tests off
// so discard it here. // so discard it here.
return this.props.makeRequest(auth); return this.props.makeRequest(auth);
}, };
_onBusyChanged: function(busy) { _onBusyChanged = (busy) => {
// if we've started doing stuff, reset the error messages // if we've started doing stuff, reset the error messages
if (busy) { if (busy) {
this.setState({ this.setState({
@ -204,29 +200,29 @@ export default createReactClass({
// there's a new screen to show the user. This is implemented by setting // there's a new screen to show the user. This is implemented by setting
// `busy: false` in `_authStateUpdated`. // `busy: false` in `_authStateUpdated`.
// See also https://github.com/vector-im/element-web/issues/12546 // See also https://github.com/vector-im/element-web/issues/12546
}, };
_setFocus: function() { _setFocus() {
if (this._stageComponent.current && this._stageComponent.current.focus) { if (this._stageComponent.current && this._stageComponent.current.focus) {
this._stageComponent.current.focus(); this._stageComponent.current.focus();
} }
}, }
_submitAuthDict: function(authData) { _submitAuthDict = authData => {
this._authLogic.submitAuthDict(authData); this._authLogic.submitAuthDict(authData);
}, };
_onPhaseChange: function(newPhase) { _onPhaseChange = newPhase => {
if (this.props.onStagePhaseChange) { if (this.props.onStagePhaseChange) {
this.props.onStagePhaseChange(this.state.authStage, newPhase || 0); this.props.onStagePhaseChange(this.state.authStage, newPhase || 0);
} }
}, };
_onStageCancel: function() { _onStageCancel = () => {
this.props.onAuthFinished(false, ERROR_USER_CANCELLED); this.props.onAuthFinished(false, ERROR_USER_CANCELLED);
}, };
_renderCurrentStage: function() { _renderCurrentStage() {
const stage = this.state.authStage; const stage = this.state.authStage;
if (!stage) { if (!stage) {
if (this.state.busy) { if (this.state.busy) {
@ -260,16 +256,17 @@ export default createReactClass({
onCancel={this._onStageCancel} onCancel={this._onStageCancel}
/> />
); );
}, }
_onAuthStageFailed: function(e) { _onAuthStageFailed = e => {
this.props.onAuthFinished(false, 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; let error = null;
if (this.state.errorText) { if (this.state.errorText) {
error = ( error = (
@ -287,5 +284,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../index'; import * as sdk from '../../index';
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
import SdkConfig from '../../SdkConfig'; import SdkConfig from '../../SdkConfig';
@ -26,29 +25,23 @@ import AccessibleButton from '../views/elements/AccessibleButton';
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
export default createReactClass({ export default class MyGroups extends React.Component {
displayName: 'MyGroups', static contextType = MatrixClientContext;
getInitialState: function() { state = {
return { groups: null,
groups: null, error: null,
error: null, };
};
},
statics: { componentDidMount() {
contextType: MatrixClientContext,
},
componentDidMount: function() {
this._fetch(); this._fetch();
}, }
_onCreateGroupClick: function() { _onCreateGroupClick = () => {
dis.dispatch({action: 'view_create_group'}); dis.dispatch({action: 'view_create_group'});
}, };
_fetch: function() { _fetch() {
this.context.getJoinedGroups().then((result) => { this.context.getJoinedGroups().then((result) => {
this.setState({groups: result.groups, error: null}); this.setState({groups: result.groups, error: null});
}, (err) => { }, (err) => {
@ -59,9 +52,9 @@ export default createReactClass({
} }
this.setState({groups: null, error: err}); this.setState({groups: null, error: err});
}); });
}, }
render: function() { render() {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader'); const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
@ -149,5 +142,5 @@ export default createReactClass({
{ content } { content }
</div> </div>
</div>; </div>;
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
import {MatrixClientPeg} from "../../MatrixClientPeg"; import {MatrixClientPeg} from "../../MatrixClientPeg";
import * as sdk from "../../index"; import * as sdk from "../../index";
@ -25,13 +24,8 @@ import * as sdk from "../../index";
/* /*
* Component which shows the global notification list using a TimelinePanel * Component which shows the global notification list using a TimelinePanel
*/ */
const NotificationPanel = createReactClass({ class NotificationPanel extends React.Component {
displayName: 'NotificationPanel', render() {
propTypes: {
},
render: function() {
// wrap a TimelinePanel with the jump-to-event bits turned off. // wrap a TimelinePanel with the jump-to-event bits turned off.
const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
@ -45,7 +39,7 @@ const NotificationPanel = createReactClass({
if (timelineSet) { if (timelineSet) {
return ( return (
<div className="mx_NotificationPanel" role="tabpanel"> <div className="mx_NotificationPanel" role="tabpanel">
<TimelinePanel key={"NotificationPanel_" + this.props.roomId} <TimelinePanel
manageReadReceipts={false} manageReadReceipts={false}
manageReadMarkers={false} manageReadMarkers={false}
timelineSet={timelineSet} timelineSet={timelineSet}
@ -63,7 +57,7 @@ const NotificationPanel = createReactClass({
</div> </div>
); );
} }
}, }
}); }
export default NotificationPanel; export default NotificationPanel;

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../MatrixClientPeg"; import {MatrixClientPeg} from "../../MatrixClientPeg";
import * as sdk from "../../index"; import * as sdk from "../../index";
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
@ -42,16 +41,16 @@ function track(action) {
Analytics.trackEvent('RoomDirectory', action); Analytics.trackEvent('RoomDirectory', action);
} }
export default createReactClass({ export default class RoomDirectory extends React.Component {
displayName: 'RoomDirectory', static propTypes = {
propTypes: {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
constructor(props) {
super(props);
getInitialState: function() {
const selectedCommunityId = TagOrderStore.getSelectedTags()[0]; const selectedCommunityId = TagOrderStore.getSelectedTags()[0];
return { this.state = {
publicRooms: [], publicRooms: [],
loading: true, loading: true,
protocolsLoading: true, protocolsLoading: true,
@ -64,10 +63,7 @@ export default createReactClass({
: null, : null,
communityName: null, communityName: null,
}; };
},
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._unmounted = false; this._unmounted = false;
this.nextBatch = null; this.nextBatch = null;
this.filterTimeout = null; this.filterTimeout = null;
@ -115,16 +111,16 @@ export default createReactClass({
} }
this.refreshRoomList(); this.refreshRoomList();
}, }
componentWillUnmount: function() { componentWillUnmount() {
if (this.filterTimeout) { if (this.filterTimeout) {
clearTimeout(this.filterTimeout); clearTimeout(this.filterTimeout);
} }
this._unmounted = true; this._unmounted = true;
}, }
refreshRoomList: function() { refreshRoomList = () => {
if (this.state.selectedCommunityId) { if (this.state.selectedCommunityId) {
this.setState({ this.setState({
publicRooms: GroupStore.getGroupRooms(this.state.selectedCommunityId).map(r => { publicRooms: GroupStore.getGroupRooms(this.state.selectedCommunityId).map(r => {
@ -158,9 +154,9 @@ export default createReactClass({
loading: true, loading: true,
}); });
this.getMoreRooms(); this.getMoreRooms();
}, };
getMoreRooms: function() { getMoreRooms() {
if (this.state.selectedCommunityId) return Promise.resolve(); // no more rooms if (this.state.selectedCommunityId) return Promise.resolve(); // no more rooms
if (!MatrixClientPeg.get()) return Promise.resolve(); if (!MatrixClientPeg.get()) return Promise.resolve();
@ -233,7 +229,7 @@ export default createReactClass({
), ),
}); });
}); });
}, }
/** /**
* A limited interface for removing rooms from the directory. * 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 * HS admins to do this through the RoomSettings interface, but
* this needs SPEC-417. * this needs SPEC-417.
*/ */
removeFromDirectory: function(room) { removeFromDirectory(room) {
const alias = get_display_alias_for_room(room); const alias = get_display_alias_for_room(room);
const name = room.name || alias || _t('Unnamed 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) { if (ev.shiftKey && !this.state.selectedCommunityId) {
ev.preventDefault(); ev.preventDefault();
this.removeFromDirectory(room); this.removeFromDirectory(room);
} else { } else {
this.showRoom(room); this.showRoom(room);
} }
}, };
onOptionChange: function(server, instanceId) { onOptionChange = (server, instanceId) => {
// clear next batch so we don't try to load more rooms // clear next batch so we don't try to load more rooms
this.nextBatch = null; this.nextBatch = null;
this.setState({ this.setState({
@ -313,15 +309,15 @@ export default createReactClass({
// find the five gitter ones, at which point we do not want // find the five gitter ones, at which point we do not want
// to render all those rooms when switching back to 'all networks'. // to render all those rooms when switching back to 'all networks'.
// Easiest to just blow away the state & re-fetch. // Easiest to just blow away the state & re-fetch.
}, };
onFillRequest: function(backwards) { onFillRequest = (backwards) => {
if (backwards || !this.nextBatch) return Promise.resolve(false); if (backwards || !this.nextBatch) return Promise.resolve(false);
return this.getMoreRooms(); return this.getMoreRooms();
}, };
onFilterChange: function(alias) { onFilterChange = (alias) => {
this.setState({ this.setState({
filterString: alias || null, filterString: alias || null,
}); });
@ -337,9 +333,9 @@ export default createReactClass({
this.filterTimeout = null; this.filterTimeout = null;
this.refreshRoomList(); this.refreshRoomList();
}, 700); }, 700);
}, };
onFilterClear: function() { onFilterClear = () => {
// update immediately // update immediately
this.setState({ this.setState({
filterString: null, filterString: null,
@ -348,9 +344,9 @@ export default createReactClass({
if (this.filterTimeout) { if (this.filterTimeout) {
clearTimeout(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 we don't have a particular instance id selected, just show that rooms alias
if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) { if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) {
// If the user specified an alias without a domain, add on whichever server is selected // 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(); this.props.onFinished();
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
@ -401,9 +397,9 @@ export default createReactClass({
should_peek: true, should_peek: true,
}); });
ev.stopPropagation(); ev.stopPropagation();
}, };
onViewClick: function(ev, room) { onViewClick = (ev, room) => {
this.props.onFinished(); this.props.onFinished();
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
@ -411,26 +407,26 @@ export default createReactClass({
should_peek: false, should_peek: false,
}); });
ev.stopPropagation(); ev.stopPropagation();
}, };
onJoinClick: function(ev, room) { onJoinClick = (ev, room) => {
this.showRoom(room, null, true); this.showRoom(room, null, true);
ev.stopPropagation(); ev.stopPropagation();
}, };
onCreateRoomClick: function(room) { onCreateRoomClick = room => {
this.props.onFinished(); this.props.onFinished();
dis.dispatch({ dis.dispatch({
action: 'view_create_room', action: 'view_create_room',
public: true, public: true,
}); });
}, };
showRoomAlias: function(alias, autoJoin=false) { showRoomAlias(alias, autoJoin=false) {
this.showRoom(null, alias, autoJoin); this.showRoom(null, alias, autoJoin);
}, }
showRoom: function(room, room_alias, autoJoin=false) { showRoom(room, room_alias, autoJoin=false) {
this.props.onFinished(); this.props.onFinished();
const payload = { const payload = {
action: 'view_room', action: 'view_room',
@ -474,7 +470,7 @@ export default createReactClass({
payload.room_id = room.room_id; payload.room_id = room.room_id;
} }
dis.dispatch(payload); dis.dispatch(payload);
}, }
getRow(room) { getRow(room) {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
@ -540,22 +536,22 @@ export default createReactClass({
<td className="mx_RoomDirectory_join">{joinOrViewButton}</td> <td className="mx_RoomDirectory_join">{joinOrViewButton}</td>
</tr> </tr>
); );
}, }
collectScrollPanel: function(element) { collectScrollPanel = (element) => {
this.scrollPanel = element; this.scrollPanel = element;
}, };
_stringLooksLikeId: function(s, field_type) { _stringLooksLikeId(s, field_type) {
let pat = /^#[^\s]+:[^\s]/; let pat = /^#[^\s]+:[^\s]/;
if (field_type && field_type.regexp) { if (field_type && field_type.regexp) {
pat = new RegExp(field_type.regexp); pat = new RegExp(field_type.regexp);
} }
return pat.test(s); return pat.test(s);
}, }
_getFieldsForThirdPartyLocation: function(userInput, protocol, instance) { _getFieldsForThirdPartyLocation(userInput, protocol, instance) {
// make an object with the fields specified by that protocol. We // make an object with the fields specified by that protocol. We
// require that the values of all but the last field come from the // require that the values of all but the last field come from the
// instance. The last is the user input. // instance. The last is the user input.
@ -569,20 +565,20 @@ export default createReactClass({
} }
fields[requiredFields[requiredFields.length - 1]] = userInput; fields[requiredFields[requiredFields.length - 1]] = userInput;
return fields; return fields;
}, }
/** /**
* called by the parent component when PageUp/Down/etc is pressed. * called by the parent component when PageUp/Down/etc is pressed.
* *
* We pass it down to the scroll panel. * We pass it down to the scroll panel.
*/ */
handleScrollKey: function(ev) { handleScrollKey = ev => {
if (this.scrollPanel) { if (this.scrollPanel) {
this.scrollPanel.handleScrollKey(ev); this.scrollPanel.handleScrollKey(ev);
} }
}, };
render: function() { render() {
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -712,8 +708,8 @@ export default createReactClass({
</div> </div>
</BaseDialog> </BaseDialog>
); );
}, }
}); }
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom // Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
// but works with the objects we get from the public room list // but works with the objects we get from the public room list

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Matrix from 'matrix-js-sdk'; import Matrix from 'matrix-js-sdk';
import { _t, _td } from '../../languageHandler'; import { _t, _td } from '../../languageHandler';
@ -39,10 +38,8 @@ function getUnsentMessages(room) {
}); });
} }
export default createReactClass({ export default class RoomStatusBar extends React.Component {
displayName: 'RoomStatusBar', static propTypes = {
propTypes: {
// the room this statusbar is representing. // the room this statusbar is representing.
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,
// This is true when the user is alone in the room, but has also sent a message. // 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 // callback for when the status bar is displaying something and should
// be visible // be visible
onVisible: PropTypes.func, onVisible: PropTypes.func,
}, };
getInitialState: function() { state = {
return { syncState: MatrixClientPeg.get().getSyncState(),
syncState: MatrixClientPeg.get().getSyncState(), syncStateData: MatrixClientPeg.get().getSyncStateData(),
syncStateData: MatrixClientPeg.get().getSyncStateData(), unsentMessages: getUnsentMessages(this.props.room),
unsentMessages: getUnsentMessages(this.props.room), };
};
},
componentDidMount: function() { componentDidMount() {
MatrixClientPeg.get().on("sync", this.onSyncStateChange); MatrixClientPeg.get().on("sync", this.onSyncStateChange);
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated); MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
this._checkSize(); this._checkSize();
}, }
componentDidUpdate: function() { componentDidUpdate() {
this._checkSize(); this._checkSize();
}, }
componentWillUnmount: function() { componentWillUnmount() {
// we may have entirely lost our client as we're logging out before clicking login on the guest bar... // we may have entirely lost our client as we're logging out before clicking login on the guest bar...
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (client) { if (client) {
client.removeListener("sync", this.onSyncStateChange); client.removeListener("sync", this.onSyncStateChange);
client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated); client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
} }
}, }
onSyncStateChange: function(state, prevState, data) { onSyncStateChange = (state, prevState, data) => {
if (state === "SYNCING" && prevState === "SYNCING") { if (state === "SYNCING" && prevState === "SYNCING") {
return; return;
} }
@ -124,39 +119,39 @@ export default createReactClass({
syncState: state, syncState: state,
syncStateData: data, syncStateData: data,
}); });
}, };
_onResendAllClick: function() { _onResendAllClick = () => {
Resend.resendUnsentEvents(this.props.room); Resend.resendUnsentEvents(this.props.room);
dis.fire(Action.FocusComposer); dis.fire(Action.FocusComposer);
}, };
_onCancelAllClick: function() { _onCancelAllClick = () => {
Resend.cancelUnsentEvents(this.props.room); Resend.cancelUnsentEvents(this.props.room);
dis.fire(Action.FocusComposer); dis.fire(Action.FocusComposer);
}, };
_onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) { _onRoomLocalEchoUpdated = (event, room, oldEventId, oldStatus) => {
if (room.roomId !== this.props.room.roomId) return; if (room.roomId !== this.props.room.roomId) return;
this.setState({ this.setState({
unsentMessages: getUnsentMessages(this.props.room), unsentMessages: getUnsentMessages(this.props.room),
}); });
}, };
// Check whether current size is greater than 0, if yes call props.onVisible // Check whether current size is greater than 0, if yes call props.onVisible
_checkSize: function() { _checkSize() {
if (this._getSize()) { if (this._getSize()) {
if (this.props.onVisible) this.props.onVisible(); if (this.props.onVisible) this.props.onVisible();
} else { } else {
if (this.props.onHidden) this.props.onHidden(); if (this.props.onHidden) this.props.onHidden();
} }
}, }
// We don't need the actual height - just whether it is likely to have // 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 // changed - so we use '0' to indicate normal size, and other values to
// indicate other sizes. // indicate other sizes.
_getSize: function() { _getSize() {
if (this._shouldShowConnectionError() || if (this._shouldShowConnectionError() ||
this.props.hasActiveCall || this.props.hasActiveCall ||
this.props.sentMessageAndIsAlone this.props.sentMessageAndIsAlone
@ -166,10 +161,10 @@ export default createReactClass({
return STATUS_BAR_EXPANDED_LARGE; return STATUS_BAR_EXPANDED_LARGE;
} }
return STATUS_BAR_HIDDEN; return STATUS_BAR_HIDDEN;
}, }
// return suitable content for the image on the left of the status bar. // return suitable content for the image on the left of the status bar.
_getIndicator: function() { _getIndicator() {
if (this.props.hasActiveCall) { if (this.props.hasActiveCall) {
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
return ( return (
@ -182,9 +177,9 @@ export default createReactClass({
} }
return null; return null;
}, }
_shouldShowConnectionError: function() { _shouldShowConnectionError() {
// no conn bar trumps the "some not sent" msg since you can't resend without // no conn bar trumps the "some not sent" msg since you can't resend without
// a connection! // a connection!
// There's one situation in which we don't show this 'no connection' bar, and that's // 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', this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED',
); );
return this.state.syncState === "ERROR" && !errorIsMauError; return this.state.syncState === "ERROR" && !errorIsMauError;
}, }
_getUnsentMessageContent: function() { _getUnsentMessageContent() {
const unsentMessages = this.state.unsentMessages; const unsentMessages = this.state.unsentMessages;
if (!unsentMessages.length) return null; if (!unsentMessages.length) return null;
@ -272,10 +267,10 @@ export default createReactClass({
</div> </div>
</div> </div>
</div>; </div>;
}, }
// return suitable content for the main (text) part of the status bar. // return suitable content for the main (text) part of the status bar.
_getContent: function() { _getContent() {
if (this._shouldShowConnectionError()) { if (this._shouldShowConnectionError()) {
return ( return (
<div className="mx_RoomStatusBar_connectionLostBar"> <div className="mx_RoomStatusBar_connectionLostBar">
@ -323,9 +318,9 @@ export default createReactClass({
} }
return null; return null;
}, }
render: function() { render() {
const content = this._getContent(); const content = this._getContent();
const indicator = this._getIndicator(); const indicator = this._getIndicator();
@ -339,5 +334,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -24,7 +24,6 @@ limitations under the License.
import shouldHideEvent from '../../shouldHideEvent'; import shouldHideEvent from '../../shouldHideEvent';
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
@ -68,9 +67,8 @@ if (DEBUG) {
debuglog = console.log.bind(console); debuglog = console.log.bind(console);
} }
export default createReactClass({ export default class RoomView extends React.Component {
displayName: 'RoomView', static propTypes = {
propTypes: {
ConferenceHandler: PropTypes.any, ConferenceHandler: PropTypes.any,
// Called with the credentials of a registered user (if they were a ROU that // 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 // Servers the RoomView can use to try and assist joins
viaServers: PropTypes.arrayOf(PropTypes.string), viaServers: PropTypes.arrayOf(PropTypes.string),
}, };
statics: { static contextType = MatrixClientContext;
contextType: MatrixClientContext,
}, constructor(props) {
super(props);
getInitialState: function() {
const llMembers = this.context.hasLazyLoadMembersEnabled(); const llMembers = this.context.hasLazyLoadMembersEnabled();
return { this.state = {
room: null, room: null,
roomId: null, roomId: null,
roomLoading: true, roomLoading: true,
@ -171,10 +169,7 @@ export default createReactClass({
matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), 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.dispatcherRef = dis.register(this.onAction);
this.context.on("Room", this.onRoom); this.context.on("Room", this.onRoom);
this.context.on("Room.timeline", this.onRoomTimeline); this.context.on("Room.timeline", this.onRoomTimeline);
@ -201,15 +196,15 @@ export default createReactClass({
this._searchResultsPanel = createRef(); this._searchResultsPanel = createRef();
this._layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange); this._layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange);
}, }
_onReadReceiptsChange: function() { _onReadReceiptsChange = () => {
this.setState({ this.setState({
showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId), showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId),
}); });
}, };
_onRoomViewStoreUpdate: function(initial) { _onRoomViewStoreUpdate = initial => {
if (this.unmounted) { if (this.unmounted) {
return; return;
} }
@ -303,7 +298,7 @@ export default createReactClass({
if (initial) { if (initial) {
this._setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek); this._setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek);
} }
}, };
_getRoomId() { _getRoomId() {
// According to `_onRoomViewStoreUpdate`, `state.roomId` can be null // According to `_onRoomViewStoreUpdate`, `state.roomId` can be null
@ -312,9 +307,9 @@ export default createReactClass({
// the bare room ID. (We may want to update `state.roomId` after // the bare room ID. (We may want to update `state.roomId` after
// resolving aliases, so we could always trust it.) // resolving aliases, so we could always trust it.)
return this.state.room ? this.state.room.roomId : this.state.roomId; return this.state.room ? this.state.room.roomId : this.state.roomId;
}, }
_getPermalinkCreatorForRoom: function(room) { _getPermalinkCreatorForRoom(room) {
if (!this._permalinkCreators) this._permalinkCreators = {}; if (!this._permalinkCreators) this._permalinkCreators = {};
if (this._permalinkCreators[room.roomId]) return this._permalinkCreators[room.roomId]; if (this._permalinkCreators[room.roomId]) return this._permalinkCreators[room.roomId];
@ -327,22 +322,22 @@ export default createReactClass({
this._permalinkCreators[room.roomId].load(); this._permalinkCreators[room.roomId].load();
} }
return this._permalinkCreators[room.roomId]; return this._permalinkCreators[room.roomId];
}, }
_stopAllPermalinkCreators: function() { _stopAllPermalinkCreators() {
if (!this._permalinkCreators) return; if (!this._permalinkCreators) return;
for (const roomId of Object.keys(this._permalinkCreators)) { for (const roomId of Object.keys(this._permalinkCreators)) {
this._permalinkCreators[roomId].stop(); this._permalinkCreators[roomId].stop();
} }
}, }
_onWidgetEchoStoreUpdate: function() { _onWidgetEchoStoreUpdate = () => {
this.setState({ this.setState({
showApps: this._shouldShowApps(this.state.room), 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: // 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 peek into (search engine) (we can /peek)
// - This is a room we can publicly join or were invited to. (we can /join) // - This is a room we can publicly join or were invited to. (we can /join)
@ -404,9 +399,9 @@ export default createReactClass({
this.setState({isPeeking: false}); this.setState({isPeeking: false});
} }
} }
}, }
_shouldShowApps: function(room) { _shouldShowApps(room) {
if (!BROWSER_SUPPORTS_SANDBOX) return false; if (!BROWSER_SUPPORTS_SANDBOX) return false;
// Check if user has previously chosen to hide the app drawer for this // Check if user has previously chosen to hide the app drawer for this
@ -417,9 +412,9 @@ export default createReactClass({
// This is confusing, but it means to say that we default to the tray being // This is confusing, but it means to say that we default to the tray being
// hidden unless the user clicked to open it. // hidden unless the user clicked to open it.
return hideWidgetDrawer === "false"; return hideWidgetDrawer === "false";
}, }
componentDidMount: function() { componentDidMount() {
const call = this._getCallForRoom(); const call = this._getCallForRoom();
const callState = call ? call.call_state : "ended"; const callState = call ? call.call_state : "ended";
this.setState({ this.setState({
@ -435,14 +430,14 @@ export default createReactClass({
this.onResize(); this.onResize();
document.addEventListener("keydown", this.onNativeKeyDown); document.addEventListener("keydown", this.onNativeKeyDown);
}, }
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return (!ObjectUtils.shallowEqual(this.props, nextProps) || return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
!ObjectUtils.shallowEqual(this.state, nextState)); !ObjectUtils.shallowEqual(this.state, nextState));
}, }
componentDidUpdate: function() { componentDidUpdate() {
if (this._roomView.current) { if (this._roomView.current) {
const roomView = this._roomView.current; const roomView = this._roomView.current;
if (!roomView.ondrop) { if (!roomView.ondrop) {
@ -464,9 +459,9 @@ export default createReactClass({
atEndOfLiveTimeline: this._messagePanel.isAtEndOfLiveTimeline(), atEndOfLiveTimeline: this._messagePanel.isAtEndOfLiveTimeline(),
}); });
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
// set a boolean to say we've been unmounted, which any pending // set a boolean to say we've been unmounted, which any pending
// promises can use to throw away their results. // promises can use to throw away their results.
// //
@ -543,21 +538,21 @@ export default createReactClass({
// Tinter.tint(); // reset colourscheme // Tinter.tint(); // reset colourscheme
SettingsStore.unwatchSetting(this._layoutWatcherRef); SettingsStore.unwatchSetting(this._layoutWatcherRef);
}, }
onLayoutChange: function() { onLayoutChange = () => {
this.setState({ this.setState({
useIRCLayout: SettingsStore.getValue("useIRCLayout"), useIRCLayout: SettingsStore.getValue("useIRCLayout"),
}); });
}, };
_onRightPanelStoreUpdate: function() { _onRightPanelStoreUpdate = () => {
this.setState({ this.setState({
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
}); });
}, };
onPageUnload(event) { onPageUnload = event => {
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
return event.returnValue = return event.returnValue =
_t("You seem to be uploading files, are you sure you want to quit?"); _t("You seem to be uploading files, are you sure you want to quit?");
@ -565,10 +560,10 @@ export default createReactClass({
return event.returnValue = return event.returnValue =
_t("You seem to be in a call, are you sure you want to quit?"); _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 // 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; let handled = false;
const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev); const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev);
@ -592,9 +587,9 @@ export default createReactClass({
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
} }
}, };
onReactKeyDown: function(ev) { onReactKeyDown = ev => {
let handled = false; let handled = false;
switch (ev.key) { switch (ev.key) {
@ -613,7 +608,7 @@ export default createReactClass({
break; break;
case Key.U.toUpperCase(): case Key.U.toUpperCase():
if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) && ev.shiftKey) { if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) && ev.shiftKey) {
dis.dispatch({ action: "upload_file" }) dis.dispatch({ action: "upload_file" });
handled = true; handled = true;
} }
break; break;
@ -623,9 +618,9 @@ export default createReactClass({
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
} }
}, };
onAction: function(payload) { onAction = payload => {
switch (payload.action) { switch (payload.action) {
case 'message_send_failed': case 'message_send_failed':
case 'message_sent': case 'message_sent':
@ -709,9 +704,9 @@ export default createReactClass({
} }
break; break;
} }
}, };
onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) { onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => {
if (this.unmounted) return; if (this.unmounted) return;
// ignore events for other rooms // ignore events for other rooms
@ -747,51 +742,51 @@ export default createReactClass({
}); });
} }
} }
}, };
onRoomName: function(room) { onRoomName = room => {
if (this.state.room && room.roomId == this.state.room.roomId) { if (this.state.room && room.roomId == this.state.room.roomId) {
this.forceUpdate(); this.forceUpdate();
} }
}, };
onRoomRecoveryReminderDontAskAgain: function() { onRoomRecoveryReminderDontAskAgain = () => {
// Called when the option to not ask again is set: // Called when the option to not ask again is set:
// force an update to hide the recovery reminder // force an update to hide the recovery reminder
this.forceUpdate(); this.forceUpdate();
}, };
onKeyBackupStatus() { onKeyBackupStatus = () => {
// Key backup status changes affect whether the in-room recovery // Key backup status changes affect whether the in-room recovery
// reminder is displayed. // reminder is displayed.
this.forceUpdate(); this.forceUpdate();
}, };
canResetTimeline: function() { canResetTimeline = () => {
if (!this._messagePanel) { if (!this._messagePanel) {
return true; return true;
} }
return this._messagePanel.canResetTimeline(); return this._messagePanel.canResetTimeline();
}, };
// called when state.room is first initialised (either at initial load, // called when state.room is first initialised (either at initial load,
// after a successful peek, or after we join the room). // after a successful peek, or after we join the room).
_onRoomLoaded: function(room) { _onRoomLoaded = room => {
this._calculatePeekRules(room); this._calculatePeekRules(room);
this._updatePreviewUrlVisibility(room); this._updatePreviewUrlVisibility(room);
this._loadMembersIfJoined(room); this._loadMembersIfJoined(room);
this._calculateRecommendedVersion(room); this._calculateRecommendedVersion(room);
this._updateE2EStatus(room); this._updateE2EStatus(room);
this._updatePermissions(room); this._updatePermissions(room);
}, };
_calculateRecommendedVersion: async function(room) { async _calculateRecommendedVersion(room) {
this.setState({ this.setState({
upgradeRecommendation: await room.getRecommendedVersion(), upgradeRecommendation: await room.getRecommendedVersion(),
}); });
}, }
_loadMembersIfJoined: async function(room) { async _loadMembersIfJoined(room) {
// lazy load members if enabled // lazy load members if enabled
if (this.context.hasLazyLoadMembersEnabled()) { if (this.context.hasLazyLoadMembersEnabled()) {
if (room && room.getMyMembership() === 'join') { if (room && room.getMyMembership() === 'join') {
@ -808,9 +803,9 @@ export default createReactClass({
} }
} }
} }
}, }
_calculatePeekRules: function(room) { _calculatePeekRules(room) {
const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", ""); const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", "");
if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") { if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") {
this.setState({ this.setState({
@ -824,17 +819,17 @@ export default createReactClass({
canPeek: true, 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 // 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'; const key = this.context.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled';
this.setState({ this.setState({
showUrlPreview: SettingsStore.getValue(key, roomId), showUrlPreview: SettingsStore.getValue(key, roomId),
}); });
}, }
onRoom: function(room) { onRoom = room => {
if (!room || room.roomId !== this.state.roomId) { if (!room || room.roomId !== this.state.roomId) {
return; return;
} }
@ -843,32 +838,32 @@ export default createReactClass({
}, () => { }, () => {
this._onRoomLoaded(room); this._onRoomLoaded(room);
}); });
}, };
onDeviceVerificationChanged: function(userId, device) { onDeviceVerificationChanged = (userId, device) => {
const room = this.state.room; const room = this.state.room;
if (!room.currentState.getMember(userId)) { if (!room.currentState.getMember(userId)) {
return; return;
} }
this._updateE2EStatus(room); this._updateE2EStatus(room);
}, };
onUserVerificationChanged: function(userId, _trustStatus) { onUserVerificationChanged = (userId, _trustStatus) => {
const room = this.state.room; const room = this.state.room;
if (!room || !room.currentState.getMember(userId)) { if (!room || !room.currentState.getMember(userId)) {
return; return;
} }
this._updateE2EStatus(room); this._updateE2EStatus(room);
}, };
onCrossSigningKeysChanged: function() { onCrossSigningKeysChanged = () => {
const room = this.state.room; const room = this.state.room;
if (room) { if (room) {
this._updateE2EStatus(room); this._updateE2EStatus(room);
} }
}, };
_updateE2EStatus: async function(room) { async _updateE2EStatus(room) {
if (!this.context.isRoomEncrypted(room.roomId)) { if (!this.context.isRoomEncrypted(room.roomId)) {
return; return;
} }
@ -886,26 +881,26 @@ export default createReactClass({
this.setState({ this.setState({
e2eStatus: await shieldStatusForRoom(this.context, room), e2eStatus: await shieldStatusForRoom(this.context, room),
}); });
}, }
updateTint: function() { updateTint() {
const room = this.state.room; const room = this.state.room;
if (!room) return; if (!room) return;
console.log("Tinter.tint from updateTint"); console.log("Tinter.tint from updateTint");
const colorScheme = SettingsStore.getValue("roomColor", room.roomId); const colorScheme = SettingsStore.getValue("roomColor", room.roomId);
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color); Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
}, }
onAccountData: function(event) { onAccountData = event => {
const type = event.getType(); const type = event.getType();
if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) { 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` // non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls`
this._updatePreviewUrlVisibility(this.state.room); this._updatePreviewUrlVisibility(this.state.room);
} }
}, };
onRoomAccountData: function(event, room) { onRoomAccountData = (event, room) => {
if (room.roomId == this.state.roomId) { if (room.roomId == this.state.roomId) {
const type = event.getType(); const type = event.getType();
if (type === "org.matrix.room.color_scheme") { if (type === "org.matrix.room.color_scheme") {
@ -918,18 +913,18 @@ export default createReactClass({
this._updatePreviewUrlVisibility(room); this._updatePreviewUrlVisibility(room);
} }
} }
}, };
onRoomStateEvents: function(ev, state) { onRoomStateEvents = (ev, state) => {
// ignore if we don't have a room yet // ignore if we don't have a room yet
if (!this.state.room || this.state.room.roomId !== state.roomId) { if (!this.state.room || this.state.room.roomId !== state.roomId) {
return; return;
} }
this._updatePermissions(this.state.room); this._updatePermissions(this.state.room);
}, };
onRoomStateMember: function(ev, state, member) { onRoomStateMember = (ev, state, member) => {
// ignore if we don't have a room yet // ignore if we don't have a room yet
if (!this.state.room) { if (!this.state.room) {
return; return;
@ -941,17 +936,17 @@ export default createReactClass({
} }
this._updateRoomMembers(member); this._updateRoomMembers(member);
}, };
onMyMembership: function(room, membership, oldMembership) { onMyMembership = (room, membership, oldMembership) => {
if (room.roomId === this.state.roomId) { if (room.roomId === this.state.roomId) {
this.forceUpdate(); this.forceUpdate();
this._loadMembersIfJoined(room); this._loadMembersIfJoined(room);
this._updatePermissions(room); this._updatePermissions(room);
} }
}, };
_updatePermissions: function(room) { _updatePermissions(room) {
if (room) { if (room) {
const me = this.context.getUserId(); const me = this.context.getUserId();
const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me); const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me);
@ -959,11 +954,11 @@ export default createReactClass({
this.setState({canReact, canReply}); this.setState({canReact, canReply});
} }
}, }
// rate limited because a power level change will emit an event for every // rate limited because a power level change will emit an event for every
// member in the room. // member in the room.
_updateRoomMembers: rate_limited_func(function(dueToMember) { _updateRoomMembers = rate_limited_func((dueToMember) => {
// a member state changed in this room // a member state changed in this room
// refresh the conf call notification state // refresh the conf call notification state
this._updateConfCallNotification(); this._updateConfCallNotification();
@ -978,9 +973,9 @@ export default createReactClass({
this._checkIfAlone(this.state.room, memberCountInfluence); this._checkIfAlone(this.state.room, memberCountInfluence);
this._updateE2EStatus(this.state.room); this._updateE2EStatus(this.state.room);
}, 500), }, 500);
_checkIfAlone: function(room, countInfluence) { _checkIfAlone(room, countInfluence) {
let warnedAboutLonelyRoom = false; let warnedAboutLonelyRoom = false;
if (localStorage) { if (localStorage) {
warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId);
@ -993,9 +988,9 @@ export default createReactClass({
let joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount(); let joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount();
if (countInfluence) joinedOrInvitedMemberCount += countInfluence; if (countInfluence) joinedOrInvitedMemberCount += countInfluence;
this.setState({isAlone: joinedOrInvitedMemberCount === 1}); this.setState({isAlone: joinedOrInvitedMemberCount === 1});
}, }
_updateConfCallNotification: function() { _updateConfCallNotification() {
const room = this.state.room; const room = this.state.room;
if (!room || !this.props.ConferenceHandler) { if (!room || !this.props.ConferenceHandler) {
return; return;
@ -1017,7 +1012,7 @@ export default createReactClass({
confMember.membership === "join" confMember.membership === "join"
), ),
}); });
}, }
_updateDMState() { _updateDMState() {
const room = this.state.room; const room = this.state.room;
@ -1028,9 +1023,9 @@ export default createReactClass({
if (dmInviter) { if (dmInviter) {
Rooms.setDMRoom(room.roomId, dmInviter); Rooms.setDMRoom(room.roomId, dmInviter);
} }
}, }
onSearchResultsFillRequest: function(backwards) { onSearchResultsFillRequest = backwards => {
if (!backwards) { if (!backwards) {
return Promise.resolve(false); return Promise.resolve(false);
} }
@ -1043,25 +1038,25 @@ export default createReactClass({
debuglog("no more search results"); debuglog("no more search results");
return Promise.resolve(false); return Promise.resolve(false);
} }
}, };
onInviteButtonClick: function() { onInviteButtonClick = () => {
// call AddressPickerDialog // call AddressPickerDialog
dis.dispatch({ dis.dispatch({
action: 'view_invite', action: 'view_invite',
roomId: this.state.room.roomId, roomId: this.state.room.roomId,
}); });
this.setState({isAlone: false}); // there's a good chance they'll invite someone this.setState({isAlone: false}); // there's a good chance they'll invite someone
}, };
onStopAloneWarningClick: function() { onStopAloneWarningClick = () => {
if (localStorage) { if (localStorage) {
localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, true); localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, true);
} }
this.setState({isAlone: false}); this.setState({isAlone: false});
}, };
onJoinButtonClicked: function(ev) { onJoinButtonClicked = ev => {
// If the user is a ROU, allow them to transition to a PWLU // If the user is a ROU, allow them to transition to a PWLU
if (this.context && this.context.isGuest()) { if (this.context && this.context.isGuest()) {
// Join this room once the user has registered and logged in // Join this room once the user has registered and logged in
@ -1120,10 +1115,9 @@ export default createReactClass({
return Promise.resolve(); return Promise.resolve();
}); });
} }
};
}, onMessageListScroll = ev => {
onMessageListScroll: function(ev) {
if (this._messagePanel.isAtEndOfLiveTimeline()) { if (this._messagePanel.isAtEndOfLiveTimeline()) {
this.setState({ this.setState({
numUnreadMessages: 0, numUnreadMessages: 0,
@ -1135,9 +1129,9 @@ export default createReactClass({
}); });
} }
this._updateTopUnreadMessagesBar(); this._updateTopUnreadMessagesBar();
}, };
onDragOver: function(ev) { onDragOver = ev => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
@ -1154,9 +1148,9 @@ export default createReactClass({
ev.dataTransfer.dropEffect = 'copy'; ev.dataTransfer.dropEffect = 'copy';
} }
} }
}, };
onDrop: function(ev) { onDrop = ev => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
ContentMessages.sharedInstance().sendContentListToRoom( ContentMessages.sharedInstance().sendContentListToRoom(
@ -1164,15 +1158,15 @@ export default createReactClass({
); );
this.setState({ draggingFile: false }); this.setState({ draggingFile: false });
dis.fire(Action.FocusComposer); dis.fire(Action.FocusComposer);
}, };
onDragLeaveOrEnd: function(ev) { onDragLeaveOrEnd = ev => {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
this.setState({ draggingFile: false }); this.setState({ draggingFile: false });
}, };
injectSticker: function(url, info, text) { injectSticker(url, info, text) {
if (this.context.isGuest()) { if (this.context.isGuest()) {
dis.dispatch({action: 'require_registration'}); dis.dispatch({action: 'require_registration'});
return; return;
@ -1185,9 +1179,9 @@ export default createReactClass({
return; return;
} }
}); });
}, }
onSearch: function(term, scope) { onSearch = (term, scope) => {
this.setState({ this.setState({
searchTerm: term, searchTerm: term,
searchScope: scope, searchScope: scope,
@ -1213,9 +1207,9 @@ export default createReactClass({
debuglog("sending search request"); debuglog("sending search request");
const searchPromise = eventSearch(term, roomId); const searchPromise = eventSearch(term, roomId);
this._handleSearchResult(searchPromise); this._handleSearchResult(searchPromise);
}, };
_handleSearchResult: function(searchPromise) { _handleSearchResult(searchPromise) {
const self = this; const self = this;
// keep a record of the current search id, so that if the search terms // keep a record of the current search id, so that if the search terms
@ -1266,9 +1260,9 @@ export default createReactClass({
searchInProgress: false, searchInProgress: false,
}); });
}); });
}, }
getSearchResultTiles: function() { getSearchResultTiles() {
const SearchResultTile = sdk.getComponent('rooms.SearchResultTile'); const SearchResultTile = sdk.getComponent('rooms.SearchResultTile');
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
@ -1348,20 +1342,20 @@ export default createReactClass({
onHeightChanged={onHeightChanged} />); onHeightChanged={onHeightChanged} />);
} }
return ret; return ret;
}, }
onPinnedClick: function() { onPinnedClick = () => {
const nowShowingPinned = !this.state.showingPinned; const nowShowingPinned = !this.state.showingPinned;
const roomId = this.state.room.roomId; const roomId = this.state.room.roomId;
this.setState({showingPinned: nowShowingPinned, searching: false}); this.setState({showingPinned: nowShowingPinned, searching: false});
SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned); SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned);
}, };
onSettingsClick: function() { onSettingsClick = () => {
dis.dispatch({ action: 'open_room_settings' }); dis.dispatch({ action: 'open_room_settings' });
}, };
onCancelClick: function() { onCancelClick = () => {
console.log("updateTint from onCancelClick"); console.log("updateTint from onCancelClick");
this.updateTint(); this.updateTint();
if (this.state.forwardingEvent) { if (this.state.forwardingEvent) {
@ -1371,23 +1365,23 @@ export default createReactClass({
}); });
} }
dis.fire(Action.FocusComposer); dis.fire(Action.FocusComposer);
}, };
onLeaveClick: function() { onLeaveClick = () => {
dis.dispatch({ dis.dispatch({
action: 'leave_room', action: 'leave_room',
room_id: this.state.room.roomId, room_id: this.state.room.roomId,
}); });
}, };
onForgetClick: function() { onForgetClick = () => {
dis.dispatch({ dis.dispatch({
action: 'forget_room', action: 'forget_room',
room_id: this.state.room.roomId, room_id: this.state.room.roomId,
}); });
}, };
onRejectButtonClicked: function(ev) { onRejectButtonClicked = ev => {
const self = this; const self = this;
this.setState({ this.setState({
rejecting: true, rejecting: true,
@ -1412,9 +1406,9 @@ export default createReactClass({
rejectError: error, rejectError: error,
}); });
}); });
}, };
onRejectAndIgnoreClick: async function() { onRejectAndIgnoreClick = async () => {
this.setState({ this.setState({
rejecting: true, rejecting: true,
}); });
@ -1446,49 +1440,49 @@ export default createReactClass({
rejectError: error, rejectError: error,
}); });
} }
}, };
onRejectThreepidInviteButtonClicked: function(ev) { onRejectThreepidInviteButtonClicked = ev => {
// We can reject 3pid invites in the same way that we accept them, // We can reject 3pid invites in the same way that we accept them,
// using /leave rather than /join. In the short term though, we // using /leave rather than /join. In the short term though, we
// just ignore them. // just ignore them.
// https://github.com/vector-im/vector-web/issues/1134 // https://github.com/vector-im/vector-web/issues/1134
dis.fire(Action.ViewRoomDirectory); dis.fire(Action.ViewRoomDirectory);
}, };
onSearchClick: function() { onSearchClick = () => {
this.setState({ this.setState({
searching: !this.state.searching, searching: !this.state.searching,
showingPinned: false, showingPinned: false,
}); });
}, };
onCancelSearchClick: function() { onCancelSearchClick = () => {
this.setState({ this.setState({
searching: false, searching: false,
searchResults: null, searchResults: null,
}); });
}, };
// jump down to the bottom of this room, where new events are arriving // jump down to the bottom of this room, where new events are arriving
jumpToLiveTimeline: function() { jumpToLiveTimeline = () => {
this._messagePanel.jumpToLiveTimeline(); this._messagePanel.jumpToLiveTimeline();
dis.fire(Action.FocusComposer); dis.fire(Action.FocusComposer);
}, };
// jump up to wherever our read marker is // jump up to wherever our read marker is
jumpToReadMarker: function() { jumpToReadMarker = () => {
this._messagePanel.jumpToReadMarker(); this._messagePanel.jumpToReadMarker();
}, };
// update the read marker to match the read-receipt // update the read marker to match the read-receipt
forgetReadMarker: function(ev) { forgetReadMarker = ev => {
ev.stopPropagation(); ev.stopPropagation();
this._messagePanel.forgetReadMarker(); this._messagePanel.forgetReadMarker();
}, };
// decide whether or not the top 'unread messages' bar should be shown // decide whether or not the top 'unread messages' bar should be shown
_updateTopUnreadMessagesBar: function() { _updateTopUnreadMessagesBar = () => {
if (!this._messagePanel) { if (!this._messagePanel) {
return; return;
} }
@ -1497,12 +1491,12 @@ export default createReactClass({
if (this.state.showTopUnreadMessagesBar != showBar) { if (this.state.showTopUnreadMessagesBar != showBar) {
this.setState({showTopUnreadMessagesBar: showBar}); this.setState({showTopUnreadMessagesBar: showBar});
} }
}, };
// get the current scroll position of the room, so that it can be // get the current scroll position of the room, so that it can be
// restored when we switch back to it. // restored when we switch back to it.
// //
_getScrollState: function() { _getScrollState() {
const messagePanel = this._messagePanel; const messagePanel = this._messagePanel;
if (!messagePanel) return null; if (!messagePanel) return null;
@ -1537,9 +1531,9 @@ export default createReactClass({
focussedEvent: scrollState.trackedScrollToken, focussedEvent: scrollState.trackedScrollToken,
pixelOffset: scrollState.pixelOffset, pixelOffset: scrollState.pixelOffset,
}; };
}, }
onResize: function() { onResize = () => {
// It seems flexbox doesn't give us a way to constrain the auxPanel height to have // 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 // 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 // so we have to do it via JS instead. In this implementation we cap the height by putting
@ -1557,16 +1551,16 @@ export default createReactClass({
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50; if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); this.setState({auxPanelMaxHeight: auxPanelMaxHeight});
}, };
onFullscreenClick: function() { onFullscreenClick = () => {
dis.dispatch({ dis.dispatch({
action: 'video_fullscreen', action: 'video_fullscreen',
fullscreen: true, fullscreen: true,
}, true); }, true);
}, };
onMuteAudioClick: function() { onMuteAudioClick = () => {
const call = this._getCallForRoom(); const call = this._getCallForRoom();
if (!call) { if (!call) {
return; return;
@ -1574,9 +1568,9 @@ export default createReactClass({
const newState = !call.isMicrophoneMuted(); const newState = !call.isMicrophoneMuted();
call.setMicrophoneMuted(newState); call.setMicrophoneMuted(newState);
this.forceUpdate(); // TODO: just update the voip buttons this.forceUpdate(); // TODO: just update the voip buttons
}, };
onMuteVideoClick: function() { onMuteVideoClick = () => {
const call = this._getCallForRoom(); const call = this._getCallForRoom();
if (!call) { if (!call) {
return; return;
@ -1584,29 +1578,29 @@ export default createReactClass({
const newState = !call.isLocalVideoMuted(); const newState = !call.isLocalVideoMuted();
call.setLocalVideoMuted(newState); call.setLocalVideoMuted(newState);
this.forceUpdate(); // TODO: just update the voip buttons this.forceUpdate(); // TODO: just update the voip buttons
}, };
onStatusBarVisible: function() { onStatusBarVisible = () => {
if (this.unmounted) return; if (this.unmounted) return;
this.setState({ this.setState({
statusBarVisible: true, statusBarVisible: true,
}); });
}, };
onStatusBarHidden: function() { onStatusBarHidden = () => {
// This is currently not desired as it is annoying if it keeps expanding and collapsing // This is currently not desired as it is annoying if it keeps expanding and collapsing
if (this.unmounted) return; if (this.unmounted) return;
this.setState({ this.setState({
statusBarVisible: false, statusBarVisible: false,
}); });
}, };
/** /**
* called by the parent component when PageUp/Down/etc is pressed. * called by the parent component when PageUp/Down/etc is pressed.
* *
* We pass it down to the scroll panel. * We pass it down to the scroll panel.
*/ */
handleScrollKey: function(ev) { handleScrollKey = ev => {
let panel; let panel;
if (this._searchResultsPanel.current) { if (this._searchResultsPanel.current) {
panel = this._searchResultsPanel.current; panel = this._searchResultsPanel.current;
@ -1617,48 +1611,48 @@ export default createReactClass({
if (panel) { if (panel) {
panel.handleScrollKey(ev); panel.handleScrollKey(ev);
} }
}, };
/** /**
* get any current call for this room * get any current call for this room
*/ */
_getCallForRoom: function() { _getCallForRoom() {
if (!this.state.room) { if (!this.state.room) {
return null; return null;
} }
return CallHandler.getCallForRoom(this.state.room.roomId); return CallHandler.getCallForRoom(this.state.room.roomId);
}, }
// this has to be a proper method rather than an unnamed function, // this has to be a proper method rather than an unnamed function,
// otherwise react calls it with null on each update. // otherwise react calls it with null on each update.
_gatherTimelinePanelRef: function(r) { _gatherTimelinePanelRef = r => {
this._messagePanel = r; this._messagePanel = r;
if (r) { if (r) {
console.log("updateTint from RoomView._gatherTimelinePanelRef"); console.log("updateTint from RoomView._gatherTimelinePanelRef");
this.updateTint(); this.updateTint();
} }
}, };
_getOldRoom: function() { _getOldRoom() {
const createEvent = this.state.room.currentState.getStateEvents("m.room.create", ""); const createEvent = this.state.room.currentState.getStateEvents("m.room.create", "");
if (!createEvent || !createEvent.getContent()['predecessor']) return null; if (!createEvent || !createEvent.getContent()['predecessor']) return null;
return this.context.getRoom(createEvent.getContent()['predecessor']['room_id']); return this.context.getRoom(createEvent.getContent()['predecessor']['room_id']);
}, }
_getHiddenHighlightCount: function() { _getHiddenHighlightCount() {
const oldRoom = this._getOldRoom(); const oldRoom = this._getOldRoom();
if (!oldRoom) return 0; if (!oldRoom) return 0;
return oldRoom.getUnreadNotificationCount('highlight'); return oldRoom.getUnreadNotificationCount('highlight');
}, }
_onHiddenHighlightsClick: function() { _onHiddenHighlightsClick = () => {
const oldRoom = this._getOldRoom(); const oldRoom = this._getOldRoom();
if (!oldRoom) return; if (!oldRoom) return;
dis.dispatch({action: "view_room", room_id: oldRoom.roomId}); dis.dispatch({action: "view_room", room_id: oldRoom.roomId});
}, };
render: function() { render() {
const RoomHeader = sdk.getComponent('rooms.RoomHeader'); const RoomHeader = sdk.getComponent('rooms.RoomHeader');
const ForwardMessage = sdk.getComponent("rooms.ForwardMessage"); const ForwardMessage = sdk.getComponent("rooms.ForwardMessage");
const AuxPanel = sdk.getComponent("rooms.AuxPanel"); const AuxPanel = sdk.getComponent("rooms.AuxPanel");
@ -2118,5 +2112,5 @@ export default createReactClass({
</main> </main>
</RoomContext.Provider> </RoomContext.Provider>
); );
}, }
}); }

View file

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import React, {createRef} from "react"; import React, {createRef} from "react";
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Key } from '../../Keyboard'; import { Key } from '../../Keyboard';
import Timer from '../../utils/Timer'; import Timer from '../../utils/Timer';
@ -84,10 +83,8 @@ if (DEBUG_SCROLL) {
* offset as normal. * offset as normal.
*/ */
export default createReactClass({ export default class ScrollPanel extends React.Component {
displayName: 'ScrollPanel', static propTypes = {
propTypes: {
/* stickyBottom: if set to true, then once the user hits the bottom of /* 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 * 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 * scroll down to show the new element, rather than preserving the
@ -149,20 +146,19 @@ export default createReactClass({
* of the wrapper * of the wrapper
*/ */
fixedChildren: PropTypes.node, fixedChildren: PropTypes.node,
}, };
getDefaultProps: function() { static defaultProps = {
return { stickyBottom: true,
stickyBottom: true, startAtBottom: true,
startAtBottom: true, onFillRequest: function(backwards) { return Promise.resolve(false); },
onFillRequest: function(backwards) { return Promise.resolve(false); }, onUnfillRequest: function(backwards, scrollToken) {},
onUnfillRequest: function(backwards, scrollToken) {}, onScroll: function() {},
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}; this._pendingFillRequests = {b: null, f: null};
if (this.props.resizeNotifier) { if (this.props.resizeNotifier) {
@ -172,13 +168,13 @@ export default createReactClass({
this.resetScrollState(); this.resetScrollState();
this._itemlist = createRef(); this._itemlist = createRef();
}, }
componentDidMount: function() { componentDidMount() {
this.checkScroll(); this.checkScroll();
}, }
componentDidUpdate: function() { componentDidUpdate() {
// after adding event tiles, we may need to tweak the scroll (either to // 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 // keep at the bottom of the timeline, or to maintain the view after
// adding events to the top). // adding events to the top).
@ -186,9 +182,9 @@ export default createReactClass({
// This will also re-check the fill state, in case the paginate was inadequate // This will also re-check the fill state, in case the paginate was inadequate
this.checkScroll(); this.checkScroll();
this.updatePreventShrinking(); this.updatePreventShrinking();
}, }
componentWillUnmount: function() { componentWillUnmount() {
// set a boolean to say we've been unmounted, which any pending // set a boolean to say we've been unmounted, which any pending
// promises can use to throw away their results. // promises can use to throw away their results.
// //
@ -198,41 +194,41 @@ export default createReactClass({
if (this.props.resizeNotifier) { if (this.props.resizeNotifier) {
this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize); this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize);
} }
}, }
onScroll: function(ev) { onScroll = ev => {
debuglog("onScroll", this._getScrollNode().scrollTop); debuglog("onScroll", this._getScrollNode().scrollTop);
this._scrollTimeout.restart(); this._scrollTimeout.restart();
this._saveScrollState(); this._saveScrollState();
this.updatePreventShrinking(); this.updatePreventShrinking();
this.props.onScroll(ev); this.props.onScroll(ev);
this.checkFillState(); this.checkFillState();
}, };
onResize: function() { onResize = () => {
this.checkScroll(); this.checkScroll();
// update preventShrinkingState if present // update preventShrinkingState if present
if (this.preventShrinkingState) { if (this.preventShrinkingState) {
this.preventShrinking(); this.preventShrinking();
} }
}, };
// after an update to the contents of the panel, check that the scroll is // 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. // where it ought to be, and set off pagination requests if necessary.
checkScroll: function() { checkScroll = () => {
if (this.unmounted) { if (this.unmounted) {
return; return;
} }
this._restoreSavedScrollState(); this._restoreSavedScrollState();
this.checkFillState(); this.checkFillState();
}, };
// return true if the content is fully scrolled down right now; else false. // return true if the content is fully scrolled down right now; else false.
// //
// note that this is independent of the 'stuckAtBottom' state - it is simply // note that this is independent of the 'stuckAtBottom' state - it is simply
// about whether the content is scrolled down right now, irrespective of // about whether the content is scrolled down right now, irrespective of
// whether it will stay that way when the children update. // whether it will stay that way when the children update.
isAtBottom: function() { isAtBottom = () => {
const sn = this._getScrollNode(); const sn = this._getScrollNode();
// fractional values (both too big and too small) // fractional values (both too big and too small)
// for scrollTop happen on certain browsers/platforms // for scrollTop happen on certain browsers/platforms
@ -240,7 +236,7 @@ export default createReactClass({
// so check difference <= 1; // so check difference <= 1;
return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1; return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1;
}, };
// returns the vertical height in the given direction that can be removed from // 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 // the content box (which has a height of scrollHeight, see checkFillState) without
@ -273,7 +269,7 @@ export default createReactClass({
// |#########| - | // |#########| - |
// |#########| | // |#########| |
// `---------' - // `---------' -
_getExcessHeight: function(backwards) { _getExcessHeight(backwards) {
const sn = this._getScrollNode(); const sn = this._getScrollNode();
const contentHeight = this._getMessagesHeight(); const contentHeight = this._getMessagesHeight();
const listHeight = this._getListHeight(); const listHeight = this._getListHeight();
@ -285,10 +281,10 @@ export default createReactClass({
} else { } else {
return contentHeight - (unclippedScrollTop + 2*sn.clientHeight) - UNPAGINATION_PADDING; return contentHeight - (unclippedScrollTop + 2*sn.clientHeight) - UNPAGINATION_PADDING;
} }
}, }
// check the scroll state and send out backfill requests if necessary. // check the scroll state and send out backfill requests if necessary.
checkFillState: async function(depth=0) { checkFillState = async (depth=0) => {
if (this.unmounted) { if (this.unmounted) {
return; return;
} }
@ -368,10 +364,10 @@ export default createReactClass({
this._fillRequestWhileRunning = false; this._fillRequestWhileRunning = false;
this.checkFillState(); this.checkFillState();
} }
}, };
// check if unfilling is possible and send an unfill request if necessary // check if unfilling is possible and send an unfill request if necessary
_checkUnfillState: function(backwards) { _checkUnfillState(backwards) {
let excessHeight = this._getExcessHeight(backwards); let excessHeight = this._getExcessHeight(backwards);
if (excessHeight <= 0) { if (excessHeight <= 0) {
return; return;
@ -417,10 +413,10 @@ export default createReactClass({
this.props.onUnfillRequest(backwards, markerScrollToken); this.props.onUnfillRequest(backwards, markerScrollToken);
}, UNFILL_REQUEST_DEBOUNCE_MS); }, UNFILL_REQUEST_DEBOUNCE_MS);
} }
}, }
// check if there is already a pending fill request. If not, set one off. // 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'; const dir = backwards ? 'b' : 'f';
if (this._pendingFillRequests[dir]) { if (this._pendingFillRequests[dir]) {
debuglog("Already a "+dir+" fill in progress - not starting another"); debuglog("Already a "+dir+" fill in progress - not starting another");
@ -456,7 +452,7 @@ export default createReactClass({
return this.checkFillState(depth + 1); return this.checkFillState(depth + 1);
} }
}); });
}, }
/* get the current scroll state. This returns an object with the following /* get the current scroll state. This returns an object with the following
* properties: * properties:
@ -472,9 +468,7 @@ export default createReactClass({
* the number of pixels the bottom of the tracked child is above the * the number of pixels the bottom of the tracked child is above the
* bottom of the scroll panel. * bottom of the scroll panel.
*/ */
getScrollState: function() { getScrollState = () => this.scrollState;
return this.scrollState;
},
/* reset the saved scroll state. /* reset the saved scroll state.
* *
@ -488,7 +482,7 @@ export default createReactClass({
* no use if no children exist yet, or if you are about to replace the * no use if no children exist yet, or if you are about to replace the
* child list.) * child list.)
*/ */
resetScrollState: function() { resetScrollState = () => {
this.scrollState = { this.scrollState = {
stuckAtBottom: this.props.startAtBottom, stuckAtBottom: this.props.startAtBottom,
}; };
@ -496,20 +490,20 @@ export default createReactClass({
this._pages = 0; this._pages = 0;
this._scrollTimeout = new Timer(100); this._scrollTimeout = new Timer(100);
this._heightUpdateInProgress = false; this._heightUpdateInProgress = false;
}, };
/** /**
* jump to the top of the content. * jump to the top of the content.
*/ */
scrollToTop: function() { scrollToTop = () => {
this._getScrollNode().scrollTop = 0; this._getScrollNode().scrollTop = 0;
this._saveScrollState(); this._saveScrollState();
}, };
/** /**
* jump to the bottom of the content. * jump to the bottom of the content.
*/ */
scrollToBottom: function() { scrollToBottom = () => {
// the easiest way to make sure that the scroll state is correctly // the easiest way to make sure that the scroll state is correctly
// saved is to do the scroll, then save the updated state. (Calculating // 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 // it ourselves is hard, and we can't rely on an onScroll callback
@ -517,25 +511,25 @@ export default createReactClass({
const sn = this._getScrollNode(); const sn = this._getScrollNode();
sn.scrollTop = sn.scrollHeight; sn.scrollTop = sn.scrollHeight;
this._saveScrollState(); this._saveScrollState();
}, };
/** /**
* Page up/down. * Page up/down.
* *
* @param {number} mult: -1 to page up, +1 to page down * @param {number} mult: -1 to page up, +1 to page down
*/ */
scrollRelative: function(mult) { scrollRelative = mult => {
const scrollNode = this._getScrollNode(); const scrollNode = this._getScrollNode();
const delta = mult * scrollNode.clientHeight * 0.5; const delta = mult * scrollNode.clientHeight * 0.5;
scrollNode.scrollBy(0, delta); scrollNode.scrollBy(0, delta);
this._saveScrollState(); this._saveScrollState();
}, };
/** /**
* Scroll up/down in response to a scroll key * Scroll up/down in response to a scroll key
* @param {object} ev the keyboard event * @param {object} ev the keyboard event
*/ */
handleScrollKey: function(ev) { handleScrollKey = ev => {
switch (ev.key) { switch (ev.key) {
case Key.PAGE_UP: case Key.PAGE_UP:
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
@ -561,7 +555,7 @@ export default createReactClass({
} }
break; break;
} }
}, };
/* Scroll the panel to bring the DOM node with the scroll token /* Scroll the panel to bring the DOM node with the scroll token
* `scrollToken` into view. * `scrollToken` into view.
@ -574,7 +568,7 @@ export default createReactClass({
* node (specifically, the bottom of it) will be positioned. If omitted, it * node (specifically, the bottom of it) will be positioned. If omitted, it
* defaults to 0. * defaults to 0.
*/ */
scrollToToken: function(scrollToken, pixelOffset, offsetBase) { scrollToToken = (scrollToken, pixelOffset, offsetBase) => {
pixelOffset = pixelOffset || 0; pixelOffset = pixelOffset || 0;
offsetBase = offsetBase || 0; offsetBase = offsetBase || 0;
@ -596,9 +590,9 @@ export default createReactClass({
scrollNode.scrollTop = (trackedNode.offsetTop - (scrollNode.clientHeight * offsetBase)) + pixelOffset; scrollNode.scrollTop = (trackedNode.offsetTop - (scrollNode.clientHeight * offsetBase)) + pixelOffset;
this._saveScrollState(); this._saveScrollState();
} }
}, };
_saveScrollState: function() { _saveScrollState() {
if (this.props.stickyBottom && this.isAtBottom()) { if (this.props.stickyBottom && this.isAtBottom()) {
this.scrollState = { stuckAtBottom: true }; this.scrollState = { stuckAtBottom: true };
debuglog("saved stuckAtBottom state"); debuglog("saved stuckAtBottom state");
@ -641,9 +635,9 @@ export default createReactClass({
bottomOffset: bottomOffset, bottomOffset: bottomOffset,
pixelOffset: bottomOffset - viewportBottom, //needed for restoring the scroll position when coming back to the room pixelOffset: bottomOffset - viewportBottom, //needed for restoring the scroll position when coming back to the room
}; };
}, }
_restoreSavedScrollState: async function() { async _restoreSavedScrollState() {
const scrollState = this.scrollState; const scrollState = this.scrollState;
if (scrollState.stuckAtBottom) { if (scrollState.stuckAtBottom) {
@ -676,7 +670,8 @@ export default createReactClass({
} else { } else {
debuglog("not updating height because request already in progress"); debuglog("not updating height because request already in progress");
} }
}, }
// need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content? // need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content?
async _updateHeight() { async _updateHeight() {
// wait until user has stopped scrolling // wait until user has stopped scrolling
@ -731,7 +726,7 @@ export default createReactClass({
debuglog("updateHeight to", {newHeight, topDiff}); debuglog("updateHeight to", {newHeight, topDiff});
} }
} }
}, }
_getTrackedNode() { _getTrackedNode() {
const scrollState = this.scrollState; const scrollState = this.scrollState;
@ -764,11 +759,11 @@ export default createReactClass({
} }
return scrollState.trackedNode; return scrollState.trackedNode;
}, }
_getListHeight() { _getListHeight() {
return this._bottomGrowth + (this._pages * PAGE_SIZE); return this._bottomGrowth + (this._pages * PAGE_SIZE);
}, }
_getMessagesHeight() { _getMessagesHeight() {
const itemlist = this._itemlist.current; const itemlist = this._itemlist.current;
@ -777,17 +772,17 @@ export default createReactClass({
const firstNodeTop = itemlist.firstElementChild ? itemlist.firstElementChild.offsetTop : 0; const firstNodeTop = itemlist.firstElementChild ? itemlist.firstElementChild.offsetTop : 0;
// 18 is itemlist padding // 18 is itemlist padding
return lastNodeBottom - firstNodeTop + (18 * 2); return lastNodeBottom - firstNodeTop + (18 * 2);
}, }
_topFromBottom(node) { _topFromBottom(node) {
// current capped height - distance from top = distance from bottom of container to top of tracked element // current capped height - distance from top = distance from bottom of container to top of tracked element
return this._itemlist.current.clientHeight - node.offsetTop; return this._itemlist.current.clientHeight - node.offsetTop;
}, }
/* get the DOM node which has the scrollTop property we care about for our /* get the DOM node which has the scrollTop property we care about for our
* message panel. * message panel.
*/ */
_getScrollNode: function() { _getScrollNode() {
if (this.unmounted) { if (this.unmounted) {
// this shouldn't happen, but when it does, turn the NPE into // this shouldn't happen, but when it does, turn the NPE into
// something more meaningful. // something more meaningful.
@ -801,18 +796,18 @@ export default createReactClass({
} }
return this._divScroll; return this._divScroll;
}, }
_collectScroll: function(divScroll) { _collectScroll = divScroll => {
this._divScroll = divScroll; this._divScroll = divScroll;
}, };
/** /**
Mark the bottom offset of the last tile so we can balance it out when Mark the bottom offset of the last tile so we can balance it out when
anything below it changes, by calling updatePreventShrinking, to keep anything below it changes, by calling updatePreventShrinking, to keep
the same minimum bottom offset, effectively preventing the timeline to shrink. the same minimum bottom offset, effectively preventing the timeline to shrink.
*/ */
preventShrinking: function() { preventShrinking = () => {
const messageList = this._itemlist.current; const messageList = this._itemlist.current;
const tiles = messageList && messageList.children; const tiles = messageList && messageList.children;
if (!messageList) { if (!messageList) {
@ -836,16 +831,16 @@ export default createReactClass({
offsetNode: lastTileNode, offsetNode: lastTileNode,
}; };
debuglog("prevent shrinking, last tile ", offsetFromBottom, "px from bottom"); debuglog("prevent shrinking, last tile ", offsetFromBottom, "px from bottom");
}, };
/** Clear shrinking prevention. Used internally, and when the timeline is reloaded. */ /** Clear shrinking prevention. Used internally, and when the timeline is reloaded. */
clearPreventShrinking: function() { clearPreventShrinking = () => {
const messageList = this._itemlist.current; const messageList = this._itemlist.current;
const balanceElement = messageList && messageList.parentElement; const balanceElement = messageList && messageList.parentElement;
if (balanceElement) balanceElement.style.paddingBottom = null; if (balanceElement) balanceElement.style.paddingBottom = null;
this.preventShrinkingState = null; this.preventShrinkingState = null;
debuglog("prevent shrinking cleared"); debuglog("prevent shrinking cleared");
}, };
/** /**
update the container padding to balance update the container padding to balance
@ -855,7 +850,7 @@ export default createReactClass({
from the bottom of the marked tile grows larger than from the bottom of the marked tile grows larger than
what it was when marking. what it was when marking.
*/ */
updatePreventShrinking: function() { updatePreventShrinking = () => {
if (this.preventShrinkingState) { if (this.preventShrinkingState) {
const sn = this._getScrollNode(); const sn = this._getScrollNode();
const scrollState = this.scrollState; const scrollState = this.scrollState;
@ -885,9 +880,9 @@ export default createReactClass({
this.clearPreventShrinking(); this.clearPreventShrinking();
} }
} }
}, };
render: function() { render() {
// TODO: the classnames on the div and ol could do with being updated to // 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. // 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. // it's not obvious why we have a separate div and ol anyway.
@ -905,5 +900,5 @@ export default createReactClass({
</div> </div>
</AutoHideScrollbar> </AutoHideScrollbar>
); );
}, }
}); }

View file

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Key } from '../../Keyboard'; import { Key } from '../../Keyboard';
import dis from '../../dispatcher/dispatcher'; import dis from '../../dispatcher/dispatcher';
@ -24,10 +23,8 @@ import { throttle } from 'lodash';
import AccessibleButton from '../../components/views/elements/AccessibleButton'; import AccessibleButton from '../../components/views/elements/AccessibleButton';
import classNames from 'classnames'; import classNames from 'classnames';
export default createReactClass({ export default class SearchBox extends React.Component {
displayName: 'SearchBox', static propTypes = {
propTypes: {
onSearch: PropTypes.func, onSearch: PropTypes.func,
onCleared: PropTypes.func, onCleared: PropTypes.func,
onKeyDown: PropTypes.func, onKeyDown: PropTypes.func,
@ -38,35 +35,32 @@ export default createReactClass({
// on room search focus action (it would be nicer to take // on room search focus action (it would be nicer to take
// this functionality out, but not obvious how that would work) // this functionality out, but not obvious how that would work)
enableRoomSearchFocus: PropTypes.bool, enableRoomSearchFocus: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { enableRoomSearchFocus: false,
enableRoomSearchFocus: false, };
};
},
getInitialState: function() { constructor(props) {
return { super(props);
this._search = createRef();
this.state = {
searchTerm: "", searchTerm: "",
blurred: true, blurred: true,
}; };
}, }
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs componentDidMount() {
UNSAFE_componentWillMount: function() {
this._search = createRef();
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
}, }
componentWillUnmount: function() { componentWillUnmount() {
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
}, }
onAction: function(payload) { onAction = payload => {
if (!this.props.enableRoomSearchFocus) return; if (!this.props.enableRoomSearchFocus) return;
switch (payload.action) { switch (payload.action) {
@ -81,51 +75,51 @@ export default createReactClass({
} }
break; break;
} }
}, };
onChange: function() { onChange = () => {
if (!this._search.current) return; if (!this._search.current) return;
this.setState({ searchTerm: this._search.current.value }); this.setState({ searchTerm: this._search.current.value });
this.onSearch(); this.onSearch();
}, };
onSearch: throttle(function() { onSearch = throttle(() => {
this.props.onSearch(this._search.current.value); 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) { switch (ev.key) {
case Key.ESCAPE: case Key.ESCAPE:
this._clearSearch("keyboard"); this._clearSearch("keyboard");
break; break;
} }
if (this.props.onKeyDown) this.props.onKeyDown(ev); if (this.props.onKeyDown) this.props.onKeyDown(ev);
}, };
_onFocus: function(ev) { _onFocus = ev => {
this.setState({blurred: false}); this.setState({blurred: false});
ev.target.select(); ev.target.select();
if (this.props.onFocus) { if (this.props.onFocus) {
this.props.onFocus(ev); this.props.onFocus(ev);
} }
}, };
_onBlur: function(ev) { _onBlur = ev => {
this.setState({blurred: true}); this.setState({blurred: true});
if (this.props.onBlur) { if (this.props.onBlur) {
this.props.onBlur(ev); this.props.onBlur(ev);
} }
}, };
_clearSearch: function(source) { _clearSearch(source) {
this._search.current.value = ""; this._search.current.value = "";
this.onChange(); this.onChange();
if (this.props.onCleared) { if (this.props.onCleared) {
this.props.onCleared(source); this.props.onCleared(source);
} }
}, }
render: function() { render() {
// check for collapsed here and // check for collapsed here and
// not at parent so we keep // not at parent so we keep
// searchTerm in our state // searchTerm in our state
@ -166,5 +160,5 @@ export default createReactClass({
{ clearButton } { clearButton }
</div> </div>
); );
}, }
}); }

View file

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import TagOrderStore from '../../stores/TagOrderStore'; import TagOrderStore from '../../stores/TagOrderStore';
import GroupActions from '../../actions/GroupActions'; import GroupActions from '../../actions/GroupActions';
@ -32,21 +31,15 @@ import AutoHideScrollbar from "./AutoHideScrollbar";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import UserTagTile from "../views/elements/UserTagTile"; import UserTagTile from "../views/elements/UserTagTile";
const TagPanel = createReactClass({ class TagPanel extends React.Component {
displayName: 'TagPanel', static contextType = MatrixClientContext;
statics: { state = {
contextType: MatrixClientContext, orderedTags: [],
}, selectedTags: [],
};
getInitialState() { componentDidMount() {
return {
orderedTags: [],
selectedTags: [],
};
},
componentDidMount: function() {
this.unmounted = false; this.unmounted = false;
this.context.on("Group.myMembership", this._onGroupMyMembership); this.context.on("Group.myMembership", this._onGroupMyMembership);
this.context.on("sync", this._onClientSync); this.context.on("sync", this._onClientSync);
@ -62,7 +55,7 @@ const TagPanel = createReactClass({
}); });
// This could be done by anything with a matrix client // This could be done by anything with a matrix client
dis.dispatch(GroupActions.fetchJoinedGroups(this.context)); dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
}, }
componentWillUnmount() { componentWillUnmount() {
this.unmounted = true; this.unmounted = true;
@ -71,14 +64,14 @@ const TagPanel = createReactClass({
if (this._tagOrderStoreToken) { if (this._tagOrderStoreToken) {
this._tagOrderStoreToken.remove(); this._tagOrderStoreToken.remove();
} }
}, }
_onGroupMyMembership() { _onGroupMyMembership = () => {
if (this.unmounted) return; if (this.unmounted) return;
dis.dispatch(GroupActions.fetchJoinedGroups(this.context)); dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
}, };
_onClientSync(syncState, prevState) { _onClientSync = (syncState, prevState) => {
// Consider the client reconnected if there is no error with syncing. // Consider the client reconnected if there is no error with syncing.
// This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP. // This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
const reconnected = syncState !== "ERROR" && prevState !== syncState; const reconnected = syncState !== "ERROR" && prevState !== syncState;
@ -86,18 +79,18 @@ const TagPanel = createReactClass({
// Load joined groups // Load joined groups
dis.dispatch(GroupActions.fetchJoinedGroups(this.context)); dis.dispatch(GroupActions.fetchJoinedGroups(this.context));
} }
}, };
onMouseDown(e) { onMouseDown = e => {
// only dispatch if its not a no-op // only dispatch if its not a no-op
if (this.state.selectedTags.length > 0) { if (this.state.selectedTags.length > 0) {
dis.dispatch({action: 'deselect_tags'}); dis.dispatch({action: 'deselect_tags'});
} }
}, };
onClearFilterClick(ev) { onClearFilterClick = ev => {
dis.dispatch({action: 'deselect_tags'}); dis.dispatch({action: 'deselect_tags'});
}, };
renderGlobalIcon() { renderGlobalIcon() {
if (!SettingsStore.getValue("feature_communities_v2_prototypes")) return null; if (!SettingsStore.getValue("feature_communities_v2_prototypes")) return null;
@ -108,7 +101,7 @@ const TagPanel = createReactClass({
<hr className="mx_TagPanel_divider" /> <hr className="mx_TagPanel_divider" />
</div> </div>
); );
}, }
render() { render() {
const DNDTagTile = sdk.getComponent('elements.DNDTagTile'); const DNDTagTile = sdk.getComponent('elements.DNDTagTile');
@ -173,6 +166,6 @@ const TagPanel = createReactClass({
</Droppable> </Droppable>
</AutoHideScrollbar> </AutoHideScrollbar>
</div>; </div>;
}, }
}); }
export default TagPanel; export default TagPanel;

View file

@ -19,7 +19,6 @@ limitations under the License.
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {EventTimeline} from "matrix-js-sdk"; import {EventTimeline} from "matrix-js-sdk";
@ -54,10 +53,8 @@ if (DEBUG) {
* *
* Also responsible for handling and sending read receipts. * Also responsible for handling and sending read receipts.
*/ */
const TimelinePanel = createReactClass({ class TimelinePanel extends React.Component {
displayName: 'TimelinePanel', static propTypes = {
propTypes: {
// The js-sdk EventTimelineSet object for the timeline sequence we are // 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 // 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 // a timeline representing. If it has a room, we maintain RRs etc for
@ -115,23 +112,35 @@ const TimelinePanel = createReactClass({
// whether to use the irc layout // whether to use the irc layout
useIRCLayout: PropTypes.bool, useIRCLayout: PropTypes.bool,
}, }
statics: { // a map from room id to read marker event timestamp
// a map from room id to read marker event timestamp static roomReadMarkerTsMap = {};
roomReadMarkerTsMap: {},
},
getDefaultProps: function() { static defaultProps = {
return { // By default, disable the timelineCap in favour of unpaginating based on
// By default, disable the timelineCap in favour of unpaginating based on // event tile heights. (See _unpaginateEvents)
// event tile heights. (See _unpaginateEvents) timelineCap: Number.MAX_VALUE,
timelineCap: Number.MAX_VALUE, className: 'mx_RoomView_messagePanel',
className: 'mx_RoomView_messagePanel', };
};
}, constructor(props) {
super(props);
debuglog("TimelinePanel: mounting");
this.lastRRSentEventId = undefined;
this.lastRMSentEventId = undefined;
this._messagePanel = createRef();
if (this.props.manageReadReceipts) {
this.updateReadReceiptOnUserActivity();
}
if (this.props.manageReadMarkers) {
this.updateReadMarkerOnUserActivity();
}
getInitialState: function() {
// XXX: we could track RM per TimelineSet rather than per Room. // XXX: we could track RM per TimelineSet rather than per Room.
// but for now we just do it per room for simplicity. // but for now we just do it per room for simplicity.
let initialReadMarker = null; let initialReadMarker = null;
@ -144,7 +153,7 @@ const TimelinePanel = createReactClass({
} }
} }
return { this.state = {
events: [], events: [],
liveEvents: [], liveEvents: [],
timelineLoading: true, // track whether our room timeline is loading timelineLoading: true, // track whether our room timeline is loading
@ -203,24 +212,6 @@ const TimelinePanel = createReactClass({
// how long to show the RM for when it's scrolled off-screen // how long to show the RM for when it's scrolled off-screen
readMarkerOutOfViewThresholdMs: SettingsStore.getValue("readMarkerOutOfViewThresholdMs"), 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); this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
@ -236,10 +227,10 @@ const TimelinePanel = createReactClass({
MatrixClientPeg.get().on("sync", this.onSync); MatrixClientPeg.get().on("sync", this.onSync);
this._initTimeline(this.props); this._initTimeline(this.props);
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(newProps) { UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.timelineSet !== this.props.timelineSet) { if (newProps.timelineSet !== this.props.timelineSet) {
// throw new Error("changing timelineSet on a TimelinePanel is not supported"); // throw new Error("changing timelineSet on a TimelinePanel is not supported");
@ -260,9 +251,9 @@ const TimelinePanel = createReactClass({
" (was " + this.props.eventId + ")"); " (was " + this.props.eventId + ")");
return this._initTimeline(newProps); return this._initTimeline(newProps);
} }
}, }
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
if (!ObjectUtils.shallowEqual(this.props, nextProps)) { if (!ObjectUtils.shallowEqual(this.props, nextProps)) {
if (DEBUG) { if (DEBUG) {
console.group("Timeline.shouldComponentUpdate: props change"); console.group("Timeline.shouldComponentUpdate: props change");
@ -284,9 +275,9 @@ const TimelinePanel = createReactClass({
} }
return false; return false;
}, }
componentWillUnmount: function() { componentWillUnmount() {
// set a boolean to say we've been unmounted, which any pending // set a boolean to say we've been unmounted, which any pending
// promises can use to throw away their results. // promises can use to throw away their results.
// //
@ -316,9 +307,9 @@ const TimelinePanel = createReactClass({
client.removeListener("Event.replaced", this.onEventReplaced); client.removeListener("Event.replaced", this.onEventReplaced);
client.removeListener("sync", this.onSync); 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) // If backwards, unpaginate from the back (i.e. the start of the timeline)
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS; const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
debuglog("TimelinePanel: unpaginating events in direction", dir); debuglog("TimelinePanel: unpaginating events in direction", dir);
@ -349,18 +340,18 @@ const TimelinePanel = createReactClass({
firstVisibleEventIndex, firstVisibleEventIndex,
}); });
} }
}, };
onPaginationRequest(timelineWindow, direction, size) { onPaginationRequest = (timelineWindow, direction, size) => {
if (this.props.onPaginationRequest) { if (this.props.onPaginationRequest) {
return this.props.onPaginationRequest(timelineWindow, direction, size); return this.props.onPaginationRequest(timelineWindow, direction, size);
} else { } else {
return timelineWindow.paginate(direction, size); return timelineWindow.paginate(direction, size);
} }
}, };
// set off a pagination request. // set off a pagination request.
onMessageListFillRequest: function(backwards) { onMessageListFillRequest = backwards => {
if (!this._shouldPaginate()) return Promise.resolve(false); if (!this._shouldPaginate()) return Promise.resolve(false);
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS; const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
@ -425,9 +416,9 @@ const TimelinePanel = createReactClass({
}); });
}); });
}); });
}, };
onMessageListScroll: function(e) { onMessageListScroll = e => {
if (this.props.onScroll) { if (this.props.onScroll) {
this.props.onScroll(e); this.props.onScroll(e);
} }
@ -447,9 +438,9 @@ const TimelinePanel = createReactClass({
// NO-OP when timeout already has set to the given value // NO-OP when timeout already has set to the given value
this._readMarkerActivityTimer.changeTimeout(timeout); this._readMarkerActivityTimer.changeTimeout(timeout);
} }
}, };
onAction: function(payload) { onAction = payload => {
if (payload.action === 'ignore_state_changed') { if (payload.action === 'ignore_state_changed') {
this.forceUpdate(); this.forceUpdate();
} }
@ -463,9 +454,9 @@ const TimelinePanel = createReactClass({
} }
}); });
} }
}, };
onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) { onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => {
// ignore events for other timeline sets // ignore events for other timeline sets
if (data.timeline.getTimelineSet() !== this.props.timelineSet) return; if (data.timeline.getTimelineSet() !== this.props.timelineSet) return;
@ -537,21 +528,19 @@ const TimelinePanel = createReactClass({
} }
}); });
}); });
}, };
onRoomTimelineReset: function(room, timelineSet) { onRoomTimelineReset = (room, timelineSet) => {
if (timelineSet !== this.props.timelineSet) return; if (timelineSet !== this.props.timelineSet) return;
if (this._messagePanel.current && this._messagePanel.current.isAtBottom()) { if (this._messagePanel.current && this._messagePanel.current.isAtBottom()) {
this._loadTimeline(); this._loadTimeline();
} }
}, };
canResetTimeline: function() { canResetTimeline = () => this._messagePanel.current && this._messagePanel.current.isAtBottom();
return this._messagePanel.current && this._messagePanel.current.isAtBottom();
},
onRoomRedaction: function(ev, room) { onRoomRedaction = (ev, room) => {
if (this.unmounted) return; if (this.unmounted) return;
// ignore events for other rooms // ignore events for other rooms
@ -560,9 +549,9 @@ const TimelinePanel = createReactClass({
// we could skip an update if the event isn't in our timeline, // we could skip an update if the event isn't in our timeline,
// but that's probably an early optimisation. // but that's probably an early optimisation.
this.forceUpdate(); this.forceUpdate();
}, };
onEventReplaced: function(replacedEvent, room) { onEventReplaced = (replacedEvent, room) => {
if (this.unmounted) return; if (this.unmounted) return;
// ignore events for other rooms // ignore events for other rooms
@ -571,27 +560,27 @@ const TimelinePanel = createReactClass({
// we could skip an update if the event isn't in our timeline, // we could skip an update if the event isn't in our timeline,
// but that's probably an early optimisation. // but that's probably an early optimisation.
this.forceUpdate(); this.forceUpdate();
}, };
onRoomReceipt: function(ev, room) { onRoomReceipt = (ev, room) => {
if (this.unmounted) return; if (this.unmounted) return;
// ignore events for other rooms // ignore events for other rooms
if (room !== this.props.timelineSet.room) return; if (room !== this.props.timelineSet.room) return;
this.forceUpdate(); this.forceUpdate();
}, };
onLocalEchoUpdated: function(ev, room, oldEventId) { onLocalEchoUpdated = (ev, room, oldEventId) => {
if (this.unmounted) return; if (this.unmounted) return;
// ignore events for other rooms // ignore events for other rooms
if (room !== this.props.timelineSet.room) return; if (room !== this.props.timelineSet.room) return;
this._reloadEvents(); this._reloadEvents();
}, };
onAccountData: function(ev, room) { onAccountData = (ev, room) => {
if (this.unmounted) return; if (this.unmounted) return;
// ignore events for other rooms // ignore events for other rooms
@ -605,9 +594,9 @@ const TimelinePanel = createReactClass({
this.setState({ this.setState({
readMarkerEventId: ev.getContent().event_id, readMarkerEventId: ev.getContent().event_id,
}, this.props.onReadMarkerUpdated); }, this.props.onReadMarkerUpdated);
}, };
onEventDecrypted: function(ev) { onEventDecrypted = ev => {
// Can be null for the notification timeline, etc. // Can be null for the notification timeline, etc.
if (!this.props.timelineSet.room) return; if (!this.props.timelineSet.room) return;
@ -620,19 +609,19 @@ const TimelinePanel = createReactClass({
if (ev.getRoomId() === this.props.timelineSet.room.roomId) { if (ev.getRoomId() === this.props.timelineSet.room.roomId) {
this.forceUpdate(); this.forceUpdate();
} }
}, };
onSync: function(state, prevState, data) { onSync = (state, prevState, data) => {
this.setState({clientSyncState: state}); this.setState({clientSyncState: state});
}, };
_readMarkerTimeout(readMarkerPosition) { _readMarkerTimeout(readMarkerPosition) {
return readMarkerPosition === 0 ? return readMarkerPosition === 0 ?
this.state.readMarkerInViewThresholdMs : this.state.readMarkerInViewThresholdMs :
this.state.readMarkerOutOfViewThresholdMs; this.state.readMarkerOutOfViewThresholdMs;
}, }
updateReadMarkerOnUserActivity: async function() { async updateReadMarkerOnUserActivity() {
const initialTimeout = this._readMarkerTimeout(this.getReadMarkerPosition()); const initialTimeout = this._readMarkerTimeout(this.getReadMarkerPosition());
this._readMarkerActivityTimer = new Timer(initialTimeout); this._readMarkerActivityTimer = new Timer(initialTimeout);
@ -644,9 +633,9 @@ const TimelinePanel = createReactClass({
// outside of try/catch to not swallow errors // outside of try/catch to not swallow errors
this.updateReadMarker(); this.updateReadMarker();
} }
}, }
updateReadReceiptOnUserActivity: async function() { async updateReadReceiptOnUserActivity() {
this._readReceiptActivityTimer = new Timer(READ_RECEIPT_INTERVAL_MS); this._readReceiptActivityTimer = new Timer(READ_RECEIPT_INTERVAL_MS);
while (this._readReceiptActivityTimer) { //unset on unmount while (this._readReceiptActivityTimer) { //unset on unmount
UserActivity.sharedInstance().timeWhileActiveNow(this._readReceiptActivityTimer); UserActivity.sharedInstance().timeWhileActiveNow(this._readReceiptActivityTimer);
@ -656,9 +645,9 @@ const TimelinePanel = createReactClass({
// outside of try/catch to not swallow errors // outside of try/catch to not swallow errors
this.sendReadReceipt(); this.sendReadReceipt();
} }
}, }
sendReadReceipt: function() { sendReadReceipt = () => {
if (SettingsStore.getValue("lowBandwidth")) return; if (SettingsStore.getValue("lowBandwidth")) return;
if (!this._messagePanel.current) return; if (!this._messagePanel.current) return;
@ -766,11 +755,11 @@ const TimelinePanel = createReactClass({
}); });
} }
} }
}, };
// if the read marker is on the screen, we can now assume we've caught up to the end // 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. // of the screen, so move the marker down to the bottom of the screen.
updateReadMarker: function() { updateReadMarker = () => {
if (!this.props.manageReadMarkers) return; if (!this.props.manageReadMarkers) return;
if (this.getReadMarkerPosition() === 1) { if (this.getReadMarkerPosition() === 1) {
// the read marker is at an event below the viewport, // the read marker is at an event below the viewport,
@ -801,11 +790,11 @@ const TimelinePanel = createReactClass({
// Send the updated read marker (along with read receipt) to the server // Send the updated read marker (along with read receipt) to the server
this.sendReadReceipt(); this.sendReadReceipt();
}, };
// advance the read marker past any events we sent ourselves. // advance the read marker past any events we sent ourselves.
_advanceReadMarkerPastMyEvents: function() { _advanceReadMarkerPastMyEvents() {
if (!this.props.manageReadMarkers) return; if (!this.props.manageReadMarkers) return;
// we call `_timelineWindow.getEvents()` rather than using // we call `_timelineWindow.getEvents()` rather than using
@ -837,11 +826,11 @@ const TimelinePanel = createReactClass({
const ev = events[i]; const ev = events[i];
this._setReadMarker(ev.getId(), ev.getTs()); this._setReadMarker(ev.getId(), ev.getTs());
}, }
/* jump down to the bottom of this room, where new events are arriving /* 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 // if we can't forward-paginate the existing timeline, then there
// is no point reloading it - just jump straight to the bottom. // is no point reloading it - just jump straight to the bottom.
// //
@ -854,12 +843,12 @@ const TimelinePanel = createReactClass({
this._messagePanel.current.scrollToBottom(); this._messagePanel.current.scrollToBottom();
} }
} }
}, };
/* scroll to show the read-up-to marker. We put it 1/3 of the way down /* scroll to show the read-up-to marker. We put it 1/3 of the way down
* the container. * the container.
*/ */
jumpToReadMarker: function() { jumpToReadMarker = () => {
if (!this.props.manageReadMarkers) return; if (!this.props.manageReadMarkers) return;
if (!this._messagePanel.current) return; if (!this._messagePanel.current) return;
if (!this.state.readMarkerEventId) return; if (!this.state.readMarkerEventId) return;
@ -883,11 +872,11 @@ const TimelinePanel = createReactClass({
// As with jumpToLiveTimeline, we want to reload the timeline around the // As with jumpToLiveTimeline, we want to reload the timeline around the
// read-marker. // read-marker.
this._loadTimeline(this.state.readMarkerEventId, 0, 1/3); this._loadTimeline(this.state.readMarkerEventId, 0, 1/3);
}, };
/* update the read-up-to marker to match the read receipt /* update the read-up-to marker to match the read receipt
*/ */
forgetReadMarker: function() { forgetReadMarker = () => {
if (!this.props.manageReadMarkers) return; if (!this.props.manageReadMarkers) return;
const rmId = this._getCurrentReadReceipt(); const rmId = this._getCurrentReadReceipt();
@ -903,17 +892,17 @@ const TimelinePanel = createReactClass({
} }
this._setReadMarker(rmId, rmTs); this._setReadMarker(rmId, rmTs);
}, };
/* return true if the content is fully scrolled down and we are /* return true if the content is fully scrolled down and we are
* at the end of the live timeline. * at the end of the live timeline.
*/ */
isAtEndOfLiveTimeline: function() { isAtEndOfLiveTimeline = () => {
return this._messagePanel.current return this._messagePanel.current
&& this._messagePanel.current.isAtBottom() && this._messagePanel.current.isAtBottom()
&& this._timelineWindow && this._timelineWindow
&& !this._timelineWindow.canPaginate(EventTimeline.FORWARDS); && !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
}, }
/* get the current scroll state. See ScrollPanel.getScrollState for /* get the current scroll state. See ScrollPanel.getScrollState for
@ -921,10 +910,10 @@ const TimelinePanel = createReactClass({
* *
* returns null if we are not mounted. * returns null if we are not mounted.
*/ */
getScrollState: function() { getScrollState = () => {
if (!this._messagePanel.current) { return null; } if (!this._messagePanel.current) { return null; }
return this._messagePanel.current.getScrollState(); return this._messagePanel.current.getScrollState();
}, };
// returns one of: // returns one of:
// //
@ -932,7 +921,7 @@ const TimelinePanel = createReactClass({
// -1: read marker is above the window // -1: read marker is above the window
// 0: read marker is visible // 0: read marker is visible
// +1: read marker is below the window // +1: read marker is below the window
getReadMarkerPosition: function() { getReadMarkerPosition = () => {
if (!this.props.manageReadMarkers) return null; if (!this.props.manageReadMarkers) return null;
if (!this._messagePanel.current) return null; if (!this._messagePanel.current) return null;
@ -953,9 +942,9 @@ const TimelinePanel = createReactClass({
} }
return null; return null;
}, };
canJumpToReadMarker: function() { canJumpToReadMarker = () => {
// 1. Do not show jump bar if neither the RM nor the RR are set. // 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. // 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 // 4. Also, if pos === null, the event might not be paginated - show the unread bar
@ -963,14 +952,14 @@ const TimelinePanel = createReactClass({
const ret = this.state.readMarkerEventId !== null && // 1. const ret = this.state.readMarkerEventId !== null && // 1.
(pos < 0 || pos === null); // 3., 4. (pos < 0 || pos === null); // 3., 4.
return ret; return ret;
}, };
/* /*
* called by the parent component when PageUp/Down/etc is pressed. * called by the parent component when PageUp/Down/etc is pressed.
* *
* We pass it down to the scroll panel. * We pass it down to the scroll panel.
*/ */
handleScrollKey: function(ev) { handleScrollKey = ev => {
if (!this._messagePanel.current) { return; } if (!this._messagePanel.current) { return; }
// jump to the live timeline on ctrl-end, rather than the end of the // jump to the live timeline on ctrl-end, rather than the end of the
@ -980,9 +969,9 @@ const TimelinePanel = createReactClass({
} else { } else {
this._messagePanel.current.handleScrollKey(ev); this._messagePanel.current.handleScrollKey(ev);
} }
}, };
_initTimeline: function(props) { _initTimeline(props) {
const initialEvent = props.eventId; const initialEvent = props.eventId;
const pixelOffset = props.eventPixelOffset; const pixelOffset = props.eventPixelOffset;
@ -994,7 +983,7 @@ const TimelinePanel = createReactClass({
} }
return this._loadTimeline(initialEvent, pixelOffset, offsetBase); return this._loadTimeline(initialEvent, pixelOffset, offsetBase);
}, }
/** /**
* (re)-load the event timeline, and initialise the scroll state, centered * (re)-load the event timeline, and initialise the scroll state, centered
@ -1012,7 +1001,7 @@ const TimelinePanel = createReactClass({
* *
* returns a promise which will resolve when the load completes. * returns a promise which will resolve when the load completes.
*/ */
_loadTimeline: function(eventId, pixelOffset, offsetBase) { _loadTimeline(eventId, pixelOffset, offsetBase) {
this._timelineWindow = new Matrix.TimelineWindow( this._timelineWindow = new Matrix.TimelineWindow(
MatrixClientPeg.get(), this.props.timelineSet, MatrixClientPeg.get(), this.props.timelineSet,
{windowLimit: this.props.timelineCap}); {windowLimit: this.props.timelineCap});
@ -1122,21 +1111,21 @@ const TimelinePanel = createReactClass({
}); });
prom.then(onLoaded, onError); prom.then(onLoaded, onError);
} }
}, }
// handle the completion of a timeline load or localEchoUpdate, by // handle the completion of a timeline load or localEchoUpdate, by
// reloading the events from the timelinewindow and pending event list into // reloading the events from the timelinewindow and pending event list into
// the state. // the state.
_reloadEvents: function() { _reloadEvents() {
// we might have switched rooms since the load started - just bin // we might have switched rooms since the load started - just bin
// the results if so. // the results if so.
if (this.unmounted) return; if (this.unmounted) return;
this.setState(this._getEvents()); this.setState(this._getEvents());
}, }
// get the list of events from the timeline window and the pending event list // get the list of events from the timeline window and the pending event list
_getEvents: function() { _getEvents() {
const events = this._timelineWindow.getEvents(); const events = this._timelineWindow.getEvents();
const firstVisibleEventIndex = this._checkForPreJoinUISI(events); const firstVisibleEventIndex = this._checkForPreJoinUISI(events);
@ -1154,7 +1143,7 @@ const TimelinePanel = createReactClass({
liveEvents, liveEvents,
firstVisibleEventIndex, firstVisibleEventIndex,
}; };
}, }
/** /**
* Check for undecryptable messages that were sent while the user was not in * Check for undecryptable messages that were sent while the user was not in
@ -1166,7 +1155,7 @@ const TimelinePanel = createReactClass({
* undecryptable event that was sent while the user was not in the room. If no * undecryptable event that was sent while the user was not in the room. If no
* such events were found, then it returns 0. * such events were found, then it returns 0.
*/ */
_checkForPreJoinUISI: function(events) { _checkForPreJoinUISI(events) {
const room = this.props.timelineSet.room; const room = this.props.timelineSet.room;
if (events.length === 0 || !room || if (events.length === 0 || !room ||
@ -1228,18 +1217,18 @@ const TimelinePanel = createReactClass({
} }
} }
return 0; return 0;
}, }
_indexForEventId: function(evId) { _indexForEventId(evId) {
for (let i = 0; i < this.state.events.length; ++i) { for (let i = 0; i < this.state.events.length; ++i) {
if (evId == this.state.events[i].getId()) { if (evId == this.state.events[i].getId()) {
return i; return i;
} }
} }
return null; return null;
}, }
_getLastDisplayedEventIndex: function(opts) { _getLastDisplayedEventIndex(opts) {
opts = opts || {}; opts = opts || {};
const ignoreOwn = opts.ignoreOwn || false; const ignoreOwn = opts.ignoreOwn || false;
const allowPartial = opts.allowPartial || false; const allowPartial = opts.allowPartial || false;
@ -1313,7 +1302,7 @@ const TimelinePanel = createReactClass({
} }
return null; return null;
}, }
/** /**
* Get the id of the event corresponding to our user's latest read-receipt. * Get the id of the event corresponding to our user's latest read-receipt.
@ -1324,7 +1313,7 @@ const TimelinePanel = createReactClass({
* SDK. * SDK.
* @return {String} the event ID * @return {String} the event ID
*/ */
_getCurrentReadReceipt: function(ignoreSynthesized) { _getCurrentReadReceipt(ignoreSynthesized) {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
// the client can be null on logout // the client can be null on logout
if (client == null) { if (client == null) {
@ -1333,9 +1322,9 @@ const TimelinePanel = createReactClass({
const myUserId = client.credentials.userId; const myUserId = client.credentials.userId;
return this.props.timelineSet.room.getEventReadUpTo(myUserId, ignoreSynthesized); return this.props.timelineSet.room.getEventReadUpTo(myUserId, ignoreSynthesized);
}, }
_setReadMarker: function(eventId, eventTs, inhibitSetState) { _setReadMarker(eventId, eventTs, inhibitSetState) {
const roomId = this.props.timelineSet.room.roomId; const roomId = this.props.timelineSet.room.roomId;
// don't update the state (and cause a re-render) if there is // don't update the state (and cause a re-render) if there is
@ -1358,9 +1347,9 @@ const TimelinePanel = createReactClass({
this.setState({ this.setState({
readMarkerEventId: eventId, readMarkerEventId: eventId,
}, this.props.onReadMarkerUpdated); }, this.props.onReadMarkerUpdated);
}, }
_shouldPaginate: function() { _shouldPaginate() {
// don't try to paginate while events in the timeline are // don't try to paginate while events in the timeline are
// still being decrypted. We don't render events while they're // still being decrypted. We don't render events while they're
// being decrypted, so they don't take up space in the timeline. // being decrypted, so they don't take up space in the timeline.
@ -1369,13 +1358,13 @@ const TimelinePanel = createReactClass({
return !this.state.events.some((e) => { return !this.state.events.some((e) => {
return e.isBeingDecrypted(); return e.isBeingDecrypted();
}); });
}, }
getRelationsForEvent(...args) { getRelationsForEvent(...args) {
return this.props.timelineSet.getRelationsForEvent(...args); return this.props.timelineSet.getRelationsForEvent(...args);
}, }
render: function() { render() {
const MessagePanel = sdk.getComponent("structures.MessagePanel"); const MessagePanel = sdk.getComponent("structures.MessagePanel");
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
@ -1456,7 +1445,7 @@ const TimelinePanel = createReactClass({
useIRCLayout={this.props.useIRCLayout} useIRCLayout={this.props.useIRCLayout}
/> />
); );
}, }
}); }
export default TimelinePanel; export default TimelinePanel;

View file

@ -16,30 +16,28 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ContentMessages from '../../ContentMessages'; import ContentMessages from '../../ContentMessages';
import dis from "../../dispatcher/dispatcher"; import dis from "../../dispatcher/dispatcher";
import filesize from "filesize"; import filesize from "filesize";
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
export default createReactClass({ export default class UploadBar extends React.Component {
displayName: 'UploadBar', static propTypes = {
propTypes: {
room: PropTypes.object, room: PropTypes.object,
}, };
componentDidMount: function() { componentDidMount() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
this.mounted = true; this.mounted = true;
}, }
componentWillUnmount: function() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
}, }
onAction: function(payload) { onAction = payload => {
switch (payload.action) { switch (payload.action) {
case 'upload_progress': case 'upload_progress':
case 'upload_finished': case 'upload_finished':
@ -48,9 +46,9 @@ export default createReactClass({
if (this.mounted) this.forceUpdate(); if (this.mounted) this.forceUpdate();
break; break;
} }
}, };
render: function() { render() {
const uploads = ContentMessages.sharedInstance().getCurrentUploads(); const uploads = ContentMessages.sharedInstance().getCurrentUploads();
// for testing UI... - also fix up the ContentMessages.getCurrentUploads().length // for testing UI... - also fix up the ContentMessages.getCurrentUploads().length
@ -105,5 +103,5 @@ export default createReactClass({
<div className="mx_UploadBar_uploadFilename">{ uploadText }</div> <div className="mx_UploadBar_uploadFilename">{ uploadText }</div>
</div> </div>
); );
}, }
}); }

View file

@ -17,24 +17,21 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import SyntaxHighlight from '../views/elements/SyntaxHighlight'; import SyntaxHighlight from '../views/elements/SyntaxHighlight';
import {_t} from "../../languageHandler"; import {_t} from "../../languageHandler";
import * as sdk from "../../index"; import * as sdk from "../../index";
export default createReactClass({ export default class ViewSource extends React.Component {
displayName: 'ViewSource', static propTypes = {
propTypes: {
content: PropTypes.object.isRequired, content: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
roomId: PropTypes.string.isRequired, roomId: PropTypes.string.isRequired,
eventId: PropTypes.string.isRequired, eventId: PropTypes.string.isRequired,
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return ( return (
<BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t('View Source')}> <BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t('View Source')}>
@ -49,5 +46,5 @@ export default createReactClass({
</div> </div>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -40,50 +39,47 @@ const PHASE_EMAIL_SENT = 3;
// User has clicked the link in email and completed reset // User has clicked the link in email and completed reset
const PHASE_DONE = 4; const PHASE_DONE = 4;
export default createReactClass({ export default class ForgotPassword extends React.Component {
displayName: 'ForgotPassword', static propTypes = {
propTypes: {
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
onServerConfigChange: PropTypes.func.isRequired, onServerConfigChange: PropTypes.func.isRequired,
onLoginClick: PropTypes.func, onLoginClick: PropTypes.func,
onComplete: PropTypes.func.isRequired, onComplete: PropTypes.func.isRequired,
}, };
getInitialState: function() { state = {
return { phase: PHASE_FORGOT,
phase: PHASE_FORGOT, email: "",
email: "", password: "",
password: "", password2: "",
password2: "", errorText: null,
errorText: null,
// We perform liveliness checks later, but for now suppress the errors. // We perform liveliness checks later, but for now suppress the errors.
// We also track the server dead errors independently of the regular errors so // We also track the server dead errors independently of the regular errors so
// that we can render it differently, and override any other error the user may // that we can render it differently, and override any other error the user may
// be seeing. // be seeing.
serverIsAlive: true, serverIsAlive: true,
serverErrorIsFatal: false, serverErrorIsFatal: false,
serverDeadError: "", serverDeadError: "",
serverRequiresIdServer: null, serverRequiresIdServer: null,
}; };
},
componentDidMount: function() { componentDidMount() {
this.reset = null; this.reset = null;
this._checkServerLiveliness(this.props.serverConfig); this._checkServerLiveliness(this.props.serverConfig);
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(newProps) { // eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl && if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return; newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
// Do a liveliness check on the new URLs // Do a liveliness check on the new URLs
this._checkServerLiveliness(newProps.serverConfig); this._checkServerLiveliness(newProps.serverConfig);
}, }
_checkServerLiveliness: async function(serverConfig) { async _checkServerLiveliness(serverConfig) {
try { try {
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls( await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
serverConfig.hsUrl, serverConfig.hsUrl,
@ -100,9 +96,9 @@ export default createReactClass({
} catch (e) { } catch (e) {
this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password")); this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password"));
} }
}, }
submitPasswordReset: function(email, password) { submitPasswordReset(email, password) {
this.setState({ this.setState({
phase: PHASE_SENDING_EMAIL, phase: PHASE_SENDING_EMAIL,
}); });
@ -117,9 +113,9 @@ export default createReactClass({
phase: PHASE_FORGOT, phase: PHASE_FORGOT,
}); });
}); });
}, }
onVerify: async function(ev) { onVerify = async ev => {
ev.preventDefault(); ev.preventDefault();
if (!this.reset) { if (!this.reset) {
console.error("onVerify called before submitPasswordReset!"); console.error("onVerify called before submitPasswordReset!");
@ -131,9 +127,9 @@ export default createReactClass({
} catch (err) { } catch (err) {
this.showErrorDialog(err.message); this.showErrorDialog(err.message);
} }
}, };
onSubmitForm: async function(ev) { onSubmitForm = async ev => {
ev.preventDefault(); ev.preventDefault();
// refresh the server errors, just in case the server came back online // refresh the server errors, just in case the server came back online
@ -166,41 +162,41 @@ export default createReactClass({
}, },
}); });
} }
}, };
onInputChanged: function(stateKey, ev) { onInputChanged = (stateKey, ev) => {
this.setState({ this.setState({
[stateKey]: ev.target.value, [stateKey]: ev.target.value,
}); });
}, };
async onServerDetailsNextPhaseClick() { onServerDetailsNextPhaseClick = async () => {
this.setState({ this.setState({
phase: PHASE_FORGOT, phase: PHASE_FORGOT,
}); });
}, };
onEditServerDetailsClick(ev) { onEditServerDetailsClick = ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.setState({ this.setState({
phase: PHASE_SERVER_DETAILS, phase: PHASE_SERVER_DETAILS,
}); });
}, };
onLoginClick: function(ev) { onLoginClick = ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.props.onLoginClick(); this.props.onLoginClick();
}, };
showErrorDialog: function(body, title) { showErrorDialog(body, title) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, { Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
title: title, title: title,
description: body, description: body,
}); });
}, }
renderServerDetails() { renderServerDetails() {
const ServerConfig = sdk.getComponent("auth.ServerConfig"); const ServerConfig = sdk.getComponent("auth.ServerConfig");
@ -218,7 +214,7 @@ export default createReactClass({
submitText={_t("Next")} submitText={_t("Next")}
submitClass="mx_Login_submit" submitClass="mx_Login_submit"
/>; />;
}, }
renderForgot() { renderForgot() {
const Field = sdk.getComponent('elements.Field'); const Field = sdk.getComponent('elements.Field');
@ -335,12 +331,12 @@ export default createReactClass({
{_t('Sign in instead')} {_t('Sign in instead')}
</a> </a>
</div>; </div>;
}, }
renderSendingEmail() { renderSendingEmail() {
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />; return <Spinner />;
}, }
renderEmailSent() { renderEmailSent() {
return <div> return <div>
@ -350,7 +346,7 @@ export default createReactClass({
<input className="mx_Login_submit" type="button" onClick={this.onVerify} <input className="mx_Login_submit" type="button" onClick={this.onVerify}
value={_t('I have verified my email address')} /> value={_t('I have verified my email address')} />
</div>; </div>;
}, }
renderDone() { renderDone() {
return <div> return <div>
@ -363,9 +359,9 @@ export default createReactClass({
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete} <input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
value={_t('Return to login screen')} /> value={_t('Return to login screen')} />
</div>; </div>;
}, }
render: function() { render() {
const AuthHeader = sdk.getComponent("auth.AuthHeader"); const AuthHeader = sdk.getComponent("auth.AuthHeader");
const AuthBody = sdk.getComponent("auth.AuthBody"); const AuthBody = sdk.getComponent("auth.AuthBody");
@ -397,5 +393,5 @@ export default createReactClass({
</AuthBody> </AuthBody>
</AuthPage> </AuthPage>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {_t, _td} from '../../../languageHandler'; import {_t, _td} from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -56,10 +55,8 @@ _td("General failure");
/** /**
* A wire component which glues together login UI components and Login logic * A wire component which glues together login UI components and Login logic
*/ */
export default createReactClass({ export default class Login extends React.Component {
displayName: 'Login', static propTypes = {
propTypes: {
// Called when the user has logged in. Params: // Called when the user has logged in. Params:
// - The object returned by the login API // - The object returned by the login API
// - The user's password, if applicable, (may be cached in memory for a // - The user's password, if applicable, (may be cached in memory for a
@ -85,10 +82,14 @@ export default createReactClass({
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
isSyncing: PropTypes.bool, isSyncing: PropTypes.bool,
}, };
getInitialState: function() { constructor(props) {
return { super(props);
this._unmounted = false;
this.state = {
busy: false, busy: false,
busyLoggingIn: null, busyLoggingIn: null,
errorText: null, errorText: null,
@ -113,11 +114,6 @@ export default createReactClass({
serverErrorIsFatal: false, serverErrorIsFatal: false,
serverDeadError: "", serverDeadError: "",
}; };
},
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._unmounted = false;
// map from login step type to a function which will render a control // map from login step type to a function which will render a control
// letting you do that login type // letting you do that login type
@ -130,11 +126,11 @@ export default createReactClass({
}; };
this._initLoginLogic(); this._initLoginLogic();
}, }
componentWillUnmount: function() { componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(newProps) { UNSAFE_componentWillReceiveProps(newProps) {
@ -143,20 +139,18 @@ export default createReactClass({
// Ensure that we end up actually logging in to the right place // Ensure that we end up actually logging in to the right place
this._initLoginLogic(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl); this._initLoginLogic(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl);
}, }
onPasswordLoginError: function(errorText) { onPasswordLoginError = errorText => {
this.setState({ this.setState({
errorText, errorText,
loginIncorrect: Boolean(errorText), loginIncorrect: Boolean(errorText),
}); });
}, };
isBusy: function() { isBusy = () => this.state.busy || this.props.busy;
return this.state.busy || this.props.busy;
},
onPasswordLogin: async function(username, phoneCountry, phoneNumber, password) { onPasswordLogin = async (username, phoneCountry, phoneNumber, password) => {
if (!this.state.serverIsAlive) { if (!this.state.serverIsAlive) {
this.setState({busy: true}); this.setState({busy: true});
// Do a quick liveliness check on the URLs // Do a quick liveliness check on the URLs
@ -263,13 +257,13 @@ export default createReactClass({
loginIncorrect: error.httpStatus === 401 || error.httpStatus === 403, loginIncorrect: error.httpStatus === 401 || error.httpStatus === 403,
}); });
}); });
}, };
onUsernameChanged: function(username) { onUsernameChanged = username => {
this.setState({ username: username }); this.setState({ username: username });
}, };
onUsernameBlur: async function(username) { onUsernameBlur = async username => {
const doWellknownLookup = username[0] === "@"; const doWellknownLookup = username[0] === "@";
this.setState({ this.setState({
username: username, username: username,
@ -314,19 +308,19 @@ export default createReactClass({
}); });
} }
} }
}, };
onPhoneCountryChanged: function(phoneCountry) { onPhoneCountryChanged = phoneCountry => {
this.setState({ phoneCountry: phoneCountry }); this.setState({ phoneCountry: phoneCountry });
}, };
onPhoneNumberChanged: function(phoneNumber) { onPhoneNumberChanged = phoneNumber => {
this.setState({ this.setState({
phoneNumber: phoneNumber, phoneNumber: phoneNumber,
}); });
}, };
onPhoneNumberBlur: function(phoneNumber) { onPhoneNumberBlur = phoneNumber => {
// Validate the phone number entered // Validate the phone number entered
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) { if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
this.setState({ this.setState({
@ -339,15 +333,15 @@ export default createReactClass({
canTryLogin: true, canTryLogin: true,
}); });
} }
}, };
onRegisterClick: function(ev) { onRegisterClick = ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.props.onRegisterClick(); this.props.onRegisterClick();
}, };
onTryRegisterClick: function(ev) { onTryRegisterClick = ev => {
const step = this._getCurrentFlowStep(); const step = this._getCurrentFlowStep();
if (step === 'm.login.sso' || step === 'm.login.cas') { if (step === 'm.login.sso' || step === 'm.login.cas') {
// If we're showing SSO it means that registration is also probably disabled, // If we're showing SSO it means that registration is also probably disabled,
@ -361,23 +355,23 @@ export default createReactClass({
// Don't intercept - just go through to the register page // Don't intercept - just go through to the register page
this.onRegisterClick(ev); this.onRegisterClick(ev);
} }
}, };
async onServerDetailsNextPhaseClick() { onServerDetailsNextPhaseClick = () => {
this.setState({ this.setState({
phase: PHASE_LOGIN, phase: PHASE_LOGIN,
}); });
}, };
onEditServerDetailsClick(ev) { onEditServerDetailsClick = ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.setState({ this.setState({
phase: PHASE_SERVER_DETAILS, phase: PHASE_SERVER_DETAILS,
}); });
}, };
_initLoginLogic: async function(hsUrl, isUrl) { async _initLoginLogic(hsUrl, isUrl) {
hsUrl = hsUrl || this.props.serverConfig.hsUrl; hsUrl = hsUrl || this.props.serverConfig.hsUrl;
isUrl = isUrl || this.props.serverConfig.isUrl; isUrl = isUrl || this.props.serverConfig.isUrl;
@ -465,9 +459,9 @@ export default createReactClass({
busy: false, busy: false,
}); });
}); });
}, }
_isSupportedFlow: function(flow) { _isSupportedFlow(flow) {
// technically the flow can have multiple steps, but no one does this // technically the flow can have multiple steps, but no one does this
// for login and loginLogic doesn't support it so we can ignore it. // for login and loginLogic doesn't support it so we can ignore it.
if (!this._stepRendererMap[flow.type]) { if (!this._stepRendererMap[flow.type]) {
@ -475,11 +469,11 @@ export default createReactClass({
return false; return false;
} }
return true; return true;
}, }
_getCurrentFlowStep: function() { _getCurrentFlowStep() {
return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null; return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null;
}, }
_errorTextFromError(err) { _errorTextFromError(err) {
let errCode = err.errcode; let errCode = err.errcode;
@ -526,7 +520,7 @@ export default createReactClass({
} }
return errorText; return errorText;
}, }
renderServerComponent() { renderServerComponent() {
const ServerConfig = sdk.getComponent("auth.ServerConfig"); const ServerConfig = sdk.getComponent("auth.ServerConfig");
@ -552,7 +546,7 @@ export default createReactClass({
delayTimeMs={250} delayTimeMs={250}
{...serverDetailsProps} {...serverDetailsProps}
/>; />;
}, }
renderLoginComponentForStep() { renderLoginComponentForStep() {
if (PHASES_ENABLED && this.state.phase !== PHASE_LOGIN) { if (PHASES_ENABLED && this.state.phase !== PHASE_LOGIN) {
@ -572,9 +566,9 @@ export default createReactClass({
} }
return null; return null;
}, }
_renderPasswordStep: function() { _renderPasswordStep() {
const PasswordLogin = sdk.getComponent('auth.PasswordLogin'); const PasswordLogin = sdk.getComponent('auth.PasswordLogin');
let onEditServerDetailsClick = null; let onEditServerDetailsClick = null;
@ -603,9 +597,9 @@ export default createReactClass({
busy={this.props.isSyncing || this.state.busyLoggingIn} busy={this.props.isSyncing || this.state.busyLoggingIn}
/> />
); );
}, }
_renderSsoStep: function(loginType) { _renderSsoStep(loginType) {
const SignInToText = sdk.getComponent('views.auth.SignInToText'); const SignInToText = sdk.getComponent('views.auth.SignInToText');
let onEditServerDetailsClick = null; let onEditServerDetailsClick = null;
@ -634,9 +628,9 @@ export default createReactClass({
/> />
</div> </div>
); );
}, }
render: function() { render() {
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
const InlineSpinner = sdk.getComponent("elements.InlineSpinner"); const InlineSpinner = sdk.getComponent("elements.InlineSpinner");
const AuthHeader = sdk.getComponent("auth.AuthHeader"); const AuthHeader = sdk.getComponent("auth.AuthHeader");
@ -704,5 +698,5 @@ export default createReactClass({
</AuthBody> </AuthBody>
</AuthPage> </AuthPage>
); );
}, }
}); }

View file

@ -15,29 +15,24 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import AuthPage from "../../views/auth/AuthPage"; import AuthPage from "../../views/auth/AuthPage";
export default createReactClass({ export default class PostRegistration extends React.Component {
displayName: 'PostRegistration', static propTypes = {
propTypes: {
onComplete: PropTypes.func.isRequired, onComplete: PropTypes.func.isRequired,
}, };
getInitialState: function() { state = {
return { avatarUrl: null,
avatarUrl: null, errorString: null,
errorString: null, busy: false,
busy: false, };
};
},
componentDidMount: function() { componentDidMount() {
// There is some assymetry between ChangeDisplayName and ChangeAvatar, // There is some assymetry between ChangeDisplayName and ChangeAvatar,
// as ChangeDisplayName will auto-get the name but ChangeAvatar expects // as ChangeDisplayName will auto-get the name but ChangeAvatar expects
// the URL to be passed to you (because it's also used for room avatars). // the URL to be passed to you (because it's also used for room avatars).
@ -55,9 +50,9 @@ export default createReactClass({
busy: false, busy: false,
}); });
}); });
}, }
render: function() { render() {
const ChangeDisplayName = sdk.getComponent('settings.ChangeDisplayName'); const ChangeDisplayName = sdk.getComponent('settings.ChangeDisplayName');
const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar'); const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
const AuthHeader = sdk.getComponent('auth.AuthHeader'); const AuthHeader = sdk.getComponent('auth.AuthHeader');
@ -78,5 +73,5 @@ export default createReactClass({
</AuthBody> </AuthBody>
</AuthPage> </AuthPage>
); );
}, }
}); }

View file

@ -19,7 +19,6 @@ limitations under the License.
import Matrix from 'matrix-js-sdk'; import Matrix from 'matrix-js-sdk';
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
@ -43,10 +42,8 @@ const PHASE_REGISTRATION = 1;
// Enable phases for registration // Enable phases for registration
const PHASES_ENABLED = true; const PHASES_ENABLED = true;
export default createReactClass({ export default class Registration extends React.Component {
displayName: 'Registration', static propTypes = {
propTypes: {
// Called when the user has logged in. Params: // Called when the user has logged in. Params:
// - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken // - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken
// - The user's password, if available and applicable (may be cached in memory // - The user's password, if available and applicable (may be cached in memory
@ -65,12 +62,13 @@ export default createReactClass({
onLoginClick: PropTypes.func.isRequired, onLoginClick: PropTypes.func.isRequired,
onServerConfigChange: PropTypes.func.isRequired, onServerConfigChange: PropTypes.func.isRequired,
defaultDeviceDisplayName: PropTypes.string, defaultDeviceDisplayName: PropTypes.string,
}, };
constructor(props) {
super(props);
getInitialState: function() {
const serverType = ServerType.getTypeFromServerConfig(this.props.serverConfig); const serverType = ServerType.getTypeFromServerConfig(this.props.serverConfig);
this.state = {
return {
busy: false, busy: false,
errorText: null, errorText: null,
// We remember the values entered by the user because // We remember the values entered by the user because
@ -118,12 +116,12 @@ export default createReactClass({
// this is the user ID that's logged in. // this is the user ID that's logged in.
differentLoggedInUserId: null, differentLoggedInUserId: null,
}; };
}, }
componentDidMount: function() { componentDidMount() {
this._unmounted = false; this._unmounted = false;
this._replaceClient(); this._replaceClient();
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(newProps) { UNSAFE_componentWillReceiveProps(newProps) {
@ -142,7 +140,7 @@ export default createReactClass({
phase: this.getDefaultPhaseForServerType(serverType), phase: this.getDefaultPhaseForServerType(serverType),
}); });
} }
}, }
getDefaultPhaseForServerType(type) { getDefaultPhaseForServerType(type) {
switch (type) { switch (type) {
@ -155,9 +153,9 @@ export default createReactClass({
case ServerType.ADVANCED: case ServerType.ADVANCED:
return PHASE_SERVER_DETAILS; return PHASE_SERVER_DETAILS;
} }
}, }
onServerTypeChange(type) { onServerTypeChange = type => {
this.setState({ this.setState({
serverType: type, serverType: type,
}); });
@ -184,9 +182,9 @@ export default createReactClass({
this.setState({ this.setState({
phase: this.getDefaultPhaseForServerType(type), phase: this.getDefaultPhaseForServerType(type),
}); });
}, };
_replaceClient: async function(serverConfig) { async _replaceClient(serverConfig) {
this.setState({ this.setState({
errorText: null, errorText: null,
serverDeadError: null, serverDeadError: null,
@ -286,18 +284,18 @@ export default createReactClass({
showGenericError(e); showGenericError(e);
} }
} }
}, }
onFormSubmit: function(formVals) { onFormSubmit = formVals => {
this.setState({ this.setState({
errorText: "", errorText: "",
busy: true, busy: true,
formVals: formVals, formVals: formVals,
doingUIAuth: true, doingUIAuth: true,
}); });
}, };
_requestEmailToken: function(emailAddress, clientSecret, sendAttempt, sessionId) { _requestEmailToken = (emailAddress, clientSecret, sendAttempt, sessionId) => {
return this.state.matrixClient.requestRegisterEmailToken( return this.state.matrixClient.requestRegisterEmailToken(
emailAddress, emailAddress,
clientSecret, clientSecret,
@ -309,9 +307,9 @@ export default createReactClass({
session_id: sessionId, session_id: sessionId,
}), }),
); );
}, }
_onUIAuthFinished: async function(success, response, extra) { _onUIAuthFinished = async (success, response, extra) => {
if (!success) { if (!success) {
let msg = response.message || response.toString(); let msg = response.message || response.toString();
// can we give a better error message? // can we give a better error message?
@ -395,9 +393,9 @@ export default createReactClass({
} }
this.setState(newState); this.setState(newState);
}, };
_setupPushers: function() { _setupPushers() {
if (!this.props.brand) { if (!this.props.brand) {
return Promise.resolve(); return Promise.resolve();
} }
@ -418,15 +416,15 @@ export default createReactClass({
}, (error) => { }, (error) => {
console.error("Couldn't get pushers: " + error); console.error("Couldn't get pushers: " + error);
}); });
}, }
onLoginClick: function(ev) { onLoginClick = ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.props.onLoginClick(); this.props.onLoginClick();
}, };
onGoToFormClicked(ev) { onGoToFormClicked = ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this._replaceClient(); this._replaceClient();
@ -435,23 +433,23 @@ export default createReactClass({
doingUIAuth: false, doingUIAuth: false,
phase: PHASE_REGISTRATION, phase: PHASE_REGISTRATION,
}); });
}, };
async onServerDetailsNextPhaseClick() { onServerDetailsNextPhaseClick = async () => {
this.setState({ this.setState({
phase: PHASE_REGISTRATION, phase: PHASE_REGISTRATION,
}); });
}, };
onEditServerDetailsClick(ev) { onEditServerDetailsClick = ev => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
this.setState({ this.setState({
phase: PHASE_SERVER_DETAILS, phase: PHASE_SERVER_DETAILS,
}); });
}, };
_makeRegisterRequest: function(auth) { _makeRegisterRequest = auth => {
// We inhibit login if we're trying to register with an email address: this // We inhibit login if we're trying to register with an email address: this
// avoids a lot of complex race conditions that can occur if we try to log // avoids a lot of complex race conditions that can occur if we try to log
// the user in one one or both of the tabs they might end up with after // the user in one one or both of the tabs they might end up with after
@ -471,20 +469,20 @@ export default createReactClass({
if (auth) registerParams.auth = auth; if (auth) registerParams.auth = auth;
if (inhibitLogin !== undefined && inhibitLogin !== null) registerParams.inhibit_login = inhibitLogin; if (inhibitLogin !== undefined && inhibitLogin !== null) registerParams.inhibit_login = inhibitLogin;
return this.state.matrixClient.registerRequest(registerParams); return this.state.matrixClient.registerRequest(registerParams);
}, };
_getUIAuthInputs: function() { _getUIAuthInputs() {
return { return {
emailAddress: this.state.formVals.email, emailAddress: this.state.formVals.email,
phoneCountry: this.state.formVals.phoneCountry, phoneCountry: this.state.formVals.phoneCountry,
phoneNumber: this.state.formVals.phoneNumber, phoneNumber: this.state.formVals.phoneNumber,
}; };
}, }
// Links to the login page shown after registration is completed are routed through this // Links to the login page shown after registration is completed are routed through this
// which checks the user hasn't already logged in somewhere else (perhaps we should do // which checks the user hasn't already logged in somewhere else (perhaps we should do
// this more generally?) // this more generally?)
_onLoginClickWithCheck: async function(ev) { _onLoginClickWithCheck = async ev => {
ev.preventDefault(); ev.preventDefault();
const sessionLoaded = await Lifecycle.loadSession({ignoreGuest: true}); const sessionLoaded = await Lifecycle.loadSession({ignoreGuest: true});
@ -492,7 +490,7 @@ export default createReactClass({
// ok fine, there's still no session: really go to the login page // ok fine, there's still no session: really go to the login page
this.props.onLoginClick(); this.props.onLoginClick();
} }
}, };
renderServerComponent() { renderServerComponent() {
const ServerTypeSelector = sdk.getComponent("auth.ServerTypeSelector"); const ServerTypeSelector = sdk.getComponent("auth.ServerTypeSelector");
@ -553,7 +551,7 @@ export default createReactClass({
/> />
{serverDetails} {serverDetails}
</div>; </div>;
}, }
renderRegisterComponent() { renderRegisterComponent() {
if (PHASES_ENABLED && this.state.phase !== PHASE_REGISTRATION) { if (PHASES_ENABLED && this.state.phase !== PHASE_REGISTRATION) {
@ -608,9 +606,9 @@ export default createReactClass({
serverRequiresIdServer={this.state.serverRequiresIdServer} serverRequiresIdServer={this.state.serverRequiresIdServer}
/>; />;
} }
}, }
render: function() { render() {
const AuthHeader = sdk.getComponent('auth.AuthHeader'); const AuthHeader = sdk.getComponent('auth.AuthHeader');
const AuthBody = sdk.getComponent("auth.AuthBody"); const AuthBody = sdk.getComponent("auth.AuthBody");
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -706,5 +704,5 @@ export default createReactClass({
</AuthBody> </AuthBody>
</AuthPage> </AuthPage>
); );
}, }
}); }

View file

@ -18,16 +18,13 @@ limitations under the License.
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
export default createReactClass({ export default class AuthFooter extends React.Component {
displayName: 'AuthFooter', render() {
render: function() {
return ( return (
<div className="mx_AuthFooter"> <div className="mx_AuthFooter">
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">{ _t("powered by Matrix") }</a> <a href="https://matrix.org" target="_blank" rel="noreferrer noopener">{ _t("powered by Matrix") }</a>
</div> </div>
); );
}, }
}); }

View file

@ -17,17 +17,14 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
export default createReactClass({ export default class AuthHeader extends React.Component {
displayName: 'AuthHeader', static propTypes = {
propTypes: {
disableLanguageSelector: PropTypes.bool, disableLanguageSelector: PropTypes.bool,
}, };
render: function() { render() {
const AuthHeaderLogo = sdk.getComponent('auth.AuthHeaderLogo'); const AuthHeaderLogo = sdk.getComponent('auth.AuthHeaderLogo');
const LanguageSelector = sdk.getComponent('views.auth.LanguageSelector'); const LanguageSelector = sdk.getComponent('views.auth.LanguageSelector');
@ -37,5 +34,5 @@ export default createReactClass({
<LanguageSelector disabled={this.props.disableLanguageSelector} /> <LanguageSelector disabled={this.props.disableLanguageSelector} />
</div> </div>
); );
}, }
}); }

View file

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -24,36 +23,31 @@ const DIV_ID = 'mx_recaptcha';
/** /**
* A pure UI component which displays a captcha form. * A pure UI component which displays a captcha form.
*/ */
export default createReactClass({ export default class CaptchaForm extends React.Component {
displayName: 'CaptchaForm', static propTypes = {
propTypes: {
sitePublicKey: PropTypes.string, sitePublicKey: PropTypes.string,
// called with the captcha response // called with the captcha response
onCaptchaResponse: PropTypes.func, onCaptchaResponse: PropTypes.func,
}, };
getDefaultProps: function() { static defaultProps = {
return { onCaptchaResponse: () => {},
onCaptchaResponse: () => {}, };
};
},
getInitialState: function() { constructor(props) {
return { super(props);
this.state = {
errorText: null, errorText: null,
}; };
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._captchaWidgetId = null; this._captchaWidgetId = null;
this._recaptchaContainer = createRef(); this._recaptchaContainer = createRef();
}, }
componentDidMount: function() { componentDidMount() {
// Just putting a script tag into the returned jsx doesn't work, annoyingly, // Just putting a script tag into the returned jsx doesn't work, annoyingly,
// so we do this instead. // so we do this instead.
if (global.grecaptcha) { if (global.grecaptcha) {
@ -68,13 +62,13 @@ export default createReactClass({
); );
this._recaptchaContainer.current.appendChild(scriptTag); this._recaptchaContainer.current.appendChild(scriptTag);
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
this._resetRecaptcha(); this._resetRecaptcha();
}, }
_renderRecaptcha: function(divId) { _renderRecaptcha(divId) {
if (!global.grecaptcha) { if (!global.grecaptcha) {
console.error("grecaptcha not loaded!"); console.error("grecaptcha not loaded!");
throw new Error("Recaptcha did not load successfully"); throw new Error("Recaptcha did not load successfully");
@ -93,15 +87,15 @@ export default createReactClass({
sitekey: publicKey, sitekey: publicKey,
callback: this.props.onCaptchaResponse, callback: this.props.onCaptchaResponse,
}); });
}, }
_resetRecaptcha: function() { _resetRecaptcha() {
if (this._captchaWidgetId !== null) { if (this._captchaWidgetId !== null) {
global.grecaptcha.reset(this._captchaWidgetId); global.grecaptcha.reset(this._captchaWidgetId);
} }
}, }
_onCaptchaLoaded: function() { _onCaptchaLoaded() {
console.log("Loaded recaptcha script."); console.log("Loaded recaptcha script.");
try { try {
this._renderRecaptcha(DIV_ID); this._renderRecaptcha(DIV_ID);
@ -110,9 +104,9 @@ export default createReactClass({
errorText: e.toString(), errorText: e.toString(),
}); });
} }
}, }
render: function() { render() {
let error = null; let error = null;
if (this.state.errorText) { if (this.state.errorText) {
error = ( error = (
@ -131,5 +125,5 @@ export default createReactClass({
{ error } { error }
</div> </div>
); );
}, }
}); }

View file

@ -16,14 +16,11 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
export default createReactClass({ export default class CustomServerDialog extends React.Component {
displayName: 'CustomServerDialog', render() {
render: function() {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
return ( return (
<div className="mx_ErrorDialog"> <div className="mx_ErrorDialog">
@ -46,5 +43,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import url from 'url'; import url from 'url';
import classnames from 'classnames'; import classnames from 'classnames';
@ -75,14 +74,10 @@ import AccessibleButton from "../elements/AccessibleButton";
export const DEFAULT_PHASE = 0; export const DEFAULT_PHASE = 0;
export const PasswordAuthEntry = createReactClass({ export class PasswordAuthEntry extends React.Component {
displayName: 'PasswordAuthEntry', static LOGIN_TYPE = "m.login.password";
statics: { static propTypes = {
LOGIN_TYPE: "m.login.password",
},
propTypes: {
matrixClient: PropTypes.object.isRequired, matrixClient: PropTypes.object.isRequired,
submitAuthDict: PropTypes.func.isRequired, submitAuthDict: PropTypes.func.isRequired,
errorText: PropTypes.string, errorText: PropTypes.string,
@ -90,19 +85,17 @@ export const PasswordAuthEntry = createReactClass({
// happen? // happen?
busy: PropTypes.bool, busy: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired, onPhaseChange: PropTypes.func.isRequired,
}, };
componentDidMount: function() { componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE); this.props.onPhaseChange(DEFAULT_PHASE);
}, }
getInitialState: function() { state = {
return { password: "",
password: "", };
};
},
_onSubmit: function(e) { _onSubmit = e => {
e.preventDefault(); e.preventDefault();
if (this.props.busy) return; if (this.props.busy) return;
@ -117,16 +110,16 @@ export const PasswordAuthEntry = createReactClass({
}, },
password: this.state.password, password: this.state.password,
}); });
}, };
_onPasswordFieldChange: function(ev) { _onPasswordFieldChange = ev => {
// enable the submit button iff the password is non-empty // enable the submit button iff the password is non-empty
this.setState({ this.setState({
password: ev.target.value, password: ev.target.value,
}); });
}, };
render: function() { render() {
const passwordBoxClass = classnames({ const passwordBoxClass = classnames({
"error": this.props.errorText, "error": this.props.errorText,
}); });
@ -176,36 +169,32 @@ export const PasswordAuthEntry = createReactClass({
{ errorSection } { errorSection }
</div> </div>
); );
}, }
}); }
export const RecaptchaAuthEntry = createReactClass({ export class RecaptchaAuthEntry extends React.Component {
displayName: 'RecaptchaAuthEntry', static LOGIN_TYPE = "m.login.recaptcha";
statics: { static propTypes = {
LOGIN_TYPE: "m.login.recaptcha",
},
propTypes: {
submitAuthDict: PropTypes.func.isRequired, submitAuthDict: PropTypes.func.isRequired,
stageParams: PropTypes.object.isRequired, stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string, errorText: PropTypes.string,
busy: PropTypes.bool, busy: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired, onPhaseChange: PropTypes.func.isRequired,
}, };
componentDidMount: function() { componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE); this.props.onPhaseChange(DEFAULT_PHASE);
}, }
_onCaptchaResponse: function(response) { _onCaptchaResponse = response => {
this.props.submitAuthDict({ this.props.submitAuthDict({
type: RecaptchaAuthEntry.LOGIN_TYPE, type: RecaptchaAuthEntry.LOGIN_TYPE,
response: response, response: response,
}); });
}, };
render: function() { render() {
if (this.props.busy) { if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
return <Loader />; return <Loader />;
@ -241,31 +230,24 @@ export const RecaptchaAuthEntry = createReactClass({
{ errorSection } { errorSection }
</div> </div>
); );
}, }
}); }
export const TermsAuthEntry = createReactClass({ export class TermsAuthEntry extends React.Component {
displayName: 'TermsAuthEntry', static LOGIN_TYPE = "m.login.terms";
statics: { static propTypes = {
LOGIN_TYPE: "m.login.terms",
},
propTypes: {
submitAuthDict: PropTypes.func.isRequired, submitAuthDict: PropTypes.func.isRequired,
stageParams: PropTypes.object.isRequired, stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string, errorText: PropTypes.string,
busy: PropTypes.bool, busy: PropTypes.bool,
showContinue: PropTypes.bool, showContinue: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired, onPhaseChange: PropTypes.func.isRequired,
}, };
componentDidMount: function() { constructor(props) {
this.props.onPhaseChange(DEFAULT_PHASE); super(props);
},
// TODO: [REACT-WARNING] Move this to constructor
componentWillMount: function() {
// example stageParams: // example stageParams:
// //
// { // {
@ -310,17 +292,22 @@ export const TermsAuthEntry = createReactClass({
pickedPolicies.push(langPolicy); pickedPolicies.push(langPolicy);
} }
this.setState({ this.state = {
"toggledPolicies": initToggles, toggledPolicies: initToggles,
"policies": pickedPolicies, policies: pickedPolicies,
}); };
}, }
tryContinue: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
}
tryContinue = () => {
this._trySubmit(); this._trySubmit();
}, };
_togglePolicy: function(policyId) { _togglePolicy(policyId) {
const newToggles = {}; const newToggles = {};
for (const policy of this.state.policies) { for (const policy of this.state.policies) {
let checked = this.state.toggledPolicies[policy.id]; let checked = this.state.toggledPolicies[policy.id];
@ -329,9 +316,9 @@ export const TermsAuthEntry = createReactClass({
newToggles[policy.id] = checked; newToggles[policy.id] = checked;
} }
this.setState({"toggledPolicies": newToggles}); this.setState({"toggledPolicies": newToggles});
}, }
_trySubmit: function() { _trySubmit = () => {
let allChecked = true; let allChecked = true;
for (const policy of this.state.policies) { for (const policy of this.state.policies) {
const checked = this.state.toggledPolicies[policy.id]; const checked = this.state.toggledPolicies[policy.id];
@ -340,9 +327,9 @@ export const TermsAuthEntry = createReactClass({
if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE}); if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
else this.setState({errorText: _t("Please review and accept all of the homeserver's policies")}); else this.setState({errorText: _t("Please review and accept all of the homeserver's policies")});
}, };
render: function() { render() {
if (this.props.busy) { if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
return <Loader />; return <Loader />;
@ -387,17 +374,13 @@ export const TermsAuthEntry = createReactClass({
{ submitButton } { submitButton }
</div> </div>
); );
}, }
}); }
export const EmailIdentityAuthEntry = createReactClass({ export class EmailIdentityAuthEntry extends React.Component {
displayName: 'EmailIdentityAuthEntry', static LOGIN_TYPE = "m.login.email.identity";
statics: { static propTypes = {
LOGIN_TYPE: "m.login.email.identity",
},
propTypes: {
matrixClient: PropTypes.object.isRequired, matrixClient: PropTypes.object.isRequired,
submitAuthDict: PropTypes.func.isRequired, submitAuthDict: PropTypes.func.isRequired,
authSessionId: PropTypes.string.isRequired, authSessionId: PropTypes.string.isRequired,
@ -407,13 +390,13 @@ export const EmailIdentityAuthEntry = createReactClass({
fail: PropTypes.func.isRequired, fail: PropTypes.func.isRequired,
setEmailSid: PropTypes.func.isRequired, setEmailSid: PropTypes.func.isRequired,
onPhaseChange: PropTypes.func.isRequired, onPhaseChange: PropTypes.func.isRequired,
}, };
componentDidMount: function() { componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE); this.props.onPhaseChange(DEFAULT_PHASE);
}, }
render: function() { render() {
// This component is now only displayed once the token has been requested, // This component is now only displayed once the token has been requested,
// so we know the email has been sent. It can also get loaded after the user // so we know the email has been sent. It can also get loaded after the user
// has clicked the validation link if the server takes a while to propagate // has clicked the validation link if the server takes a while to propagate
@ -434,17 +417,13 @@ export const EmailIdentityAuthEntry = createReactClass({
</div> </div>
); );
} }
}, }
}); }
export const MsisdnAuthEntry = createReactClass({ export class MsisdnAuthEntry extends React.Component {
displayName: 'MsisdnAuthEntry', static LOGIN_TYPE = "m.login.msisdn";
statics: { static propTypes = {
LOGIN_TYPE: "m.login.msisdn",
},
propTypes: {
inputs: PropTypes.shape({ inputs: PropTypes.shape({
phoneCountry: PropTypes.string, phoneCountry: PropTypes.string,
phoneNumber: PropTypes.string, phoneNumber: PropTypes.string,
@ -454,16 +433,14 @@ export const MsisdnAuthEntry = createReactClass({
submitAuthDict: PropTypes.func.isRequired, submitAuthDict: PropTypes.func.isRequired,
matrixClient: PropTypes.object, matrixClient: PropTypes.object,
onPhaseChange: PropTypes.func.isRequired, onPhaseChange: PropTypes.func.isRequired,
}, };
getInitialState: function() { state = {
return { token: '',
token: '', requestingToken: false,
requestingToken: false, };
};
},
componentDidMount: function() { componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE); this.props.onPhaseChange(DEFAULT_PHASE);
this._submitUrl = null; this._submitUrl = null;
@ -477,12 +454,12 @@ export const MsisdnAuthEntry = createReactClass({
}).finally(() => { }).finally(() => {
this.setState({requestingToken: false}); this.setState({requestingToken: false});
}); });
}, }
/* /*
* Requests a verification token by SMS. * Requests a verification token by SMS.
*/ */
_requestMsisdnToken: function() { _requestMsisdnToken() {
return this.props.matrixClient.requestRegisterMsisdnToken( return this.props.matrixClient.requestRegisterMsisdnToken(
this.props.inputs.phoneCountry, this.props.inputs.phoneCountry,
this.props.inputs.phoneNumber, this.props.inputs.phoneNumber,
@ -493,15 +470,15 @@ export const MsisdnAuthEntry = createReactClass({
this._sid = result.sid; this._sid = result.sid;
this._msisdn = result.msisdn; this._msisdn = result.msisdn;
}); });
}, }
_onTokenChange: function(e) { _onTokenChange = e => {
this.setState({ this.setState({
token: e.target.value, token: e.target.value,
}); });
}, };
_onFormSubmit: async function(e) { _onFormSubmit = async e => {
e.preventDefault(); e.preventDefault();
if (this.state.token == '') return; if (this.state.token == '') return;
@ -552,9 +529,9 @@ export const MsisdnAuthEntry = createReactClass({
this.props.fail(e); this.props.fail(e);
console.log("Failed to submit msisdn token"); console.log("Failed to submit msisdn token");
} }
}, };
render: function() { render() {
if (this.state.requestingToken) { if (this.state.requestingToken) {
const Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
return <Loader />; return <Loader />;
@ -598,8 +575,8 @@ export const MsisdnAuthEntry = createReactClass({
</div> </div>
); );
} }
}, }
}); }
export class SSOAuthEntry extends React.Component { export class SSOAuthEntry extends React.Component {
static propTypes = { static propTypes = {
@ -686,46 +663,46 @@ export class SSOAuthEntry extends React.Component {
} }
} }
export const FallbackAuthEntry = createReactClass({ export class FallbackAuthEntry extends React.Component {
displayName: 'FallbackAuthEntry', static propTypes = {
propTypes: {
matrixClient: PropTypes.object.isRequired, matrixClient: PropTypes.object.isRequired,
authSessionId: PropTypes.string.isRequired, authSessionId: PropTypes.string.isRequired,
loginType: PropTypes.string.isRequired, loginType: PropTypes.string.isRequired,
submitAuthDict: PropTypes.func.isRequired, submitAuthDict: PropTypes.func.isRequired,
errorText: PropTypes.string, errorText: PropTypes.string,
onPhaseChange: PropTypes.func.isRequired, onPhaseChange: PropTypes.func.isRequired,
}, };
componentDidMount: function() { constructor(props) {
this.props.onPhaseChange(DEFAULT_PHASE); super(props);
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
// we have to make the user click a button, as browsers will block // we have to make the user click a button, as browsers will block
// the popup if we open it immediately. // the popup if we open it immediately.
this._popupWindow = null; this._popupWindow = null;
window.addEventListener("message", this._onReceiveMessage); window.addEventListener("message", this._onReceiveMessage);
this._fallbackButton = createRef(); this._fallbackButton = createRef();
}, }
componentWillUnmount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
}
componentWillUnmount() {
window.removeEventListener("message", this._onReceiveMessage); window.removeEventListener("message", this._onReceiveMessage);
if (this._popupWindow) { if (this._popupWindow) {
this._popupWindow.close(); this._popupWindow.close();
} }
}, }
focus: function() { focus = () => {
if (this._fallbackButton.current) { if (this._fallbackButton.current) {
this._fallbackButton.current.focus(); this._fallbackButton.current.focus();
} }
}, };
_onShowFallbackClick: function(e) { _onShowFallbackClick = e => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -735,18 +712,18 @@ export const FallbackAuthEntry = createReactClass({
); );
this._popupWindow = window.open(url); this._popupWindow = window.open(url);
this._popupWindow.opener = null; this._popupWindow.opener = null;
}, };
_onReceiveMessage: function(event) { _onReceiveMessage = event => {
if ( if (
event.data === "authDone" && event.data === "authDone" &&
event.origin === this.props.matrixClient.getHomeserverUrl() event.origin === this.props.matrixClient.getHomeserverUrl()
) { ) {
this.props.submitAuthDict({}); this.props.submitAuthDict({});
} }
}, };
render: function() { render() {
let errorSection; let errorSection;
if (this.props.errorText) { if (this.props.errorText) {
errorSection = ( errorSection = (
@ -761,8 +738,8 @@ export const FallbackAuthEntry = createReactClass({
{errorSection} {errorSection}
</div> </div>
); );
}, }
}); }
const AuthEntryComponents = [ const AuthEntryComponents = [
PasswordAuthEntry, PasswordAuthEntry,

View file

@ -18,7 +18,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import * as Email from '../../../email'; import * as Email from '../../../email';
@ -42,10 +41,8 @@ const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from of
/** /**
* A pure UI component which displays a registration form. * A pure UI component which displays a registration form.
*/ */
export default createReactClass({ export default class RegistrationForm extends React.Component {
displayName: 'RegistrationForm', static propTypes = {
propTypes: {
// Values pre-filled in the input boxes when the component loads // Values pre-filled in the input boxes when the component loads
defaultEmail: PropTypes.string, defaultEmail: PropTypes.string,
defaultPhoneCountry: PropTypes.string, defaultPhoneCountry: PropTypes.string,
@ -58,17 +55,17 @@ export default createReactClass({
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
canSubmit: PropTypes.bool, canSubmit: PropTypes.bool,
serverRequiresIdServer: PropTypes.bool, serverRequiresIdServer: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { onValidationChange: console.error,
onValidationChange: console.error, canSubmit: true,
canSubmit: true, };
};
},
getInitialState: function() { constructor(props) {
return { super(props);
this.state = {
// Field error codes by field ID // Field error codes by field ID
fieldValid: {}, fieldValid: {},
// The ISO2 country code selected in the phone number entry // The ISO2 country code selected in the phone number entry
@ -80,9 +77,9 @@ export default createReactClass({
passwordConfirm: this.props.defaultPassword || "", passwordConfirm: this.props.defaultPassword || "",
passwordComplexity: null, passwordComplexity: null,
}; };
}, }
onSubmit: async function(ev) { onSubmit = async ev => {
ev.preventDefault(); ev.preventDefault();
if (!this.props.canSubmit) return; if (!this.props.canSubmit) return;
@ -118,7 +115,7 @@ export default createReactClass({
title: _t("Warning!"), title: _t("Warning!"),
description: desc, description: desc,
button: _t("Continue"), button: _t("Continue"),
onFinished: function(confirmed) { onFinished(confirmed) {
if (confirmed) { if (confirmed) {
self._doSubmit(ev); self._doSubmit(ev);
} }
@ -127,9 +124,9 @@ export default createReactClass({
} else { } else {
self._doSubmit(ev); self._doSubmit(ev);
} }
}, };
_doSubmit: function(ev) { _doSubmit(ev) {
const email = this.state.email.trim(); const email = this.state.email.trim();
const promise = this.props.onRegisterClick({ const promise = this.props.onRegisterClick({
username: this.state.username.trim(), username: this.state.username.trim(),
@ -145,7 +142,7 @@ export default createReactClass({
ev.target.disabled = false; ev.target.disabled = false;
}); });
} }
}, }
async verifyFieldsBeforeSubmit() { async verifyFieldsBeforeSubmit() {
// Blur the active element if any, so we first run its blur validation, // Blur the active element if any, so we first run its blur validation,
@ -196,12 +193,12 @@ export default createReactClass({
invalidField.focus(); invalidField.focus();
invalidField.validate({ allowEmpty: false, focused: true }); invalidField.validate({ allowEmpty: false, focused: true });
return false; return false;
}, }
/** /**
* @returns {boolean} true if all fields were valid last time they were validated. * @returns {boolean} true if all fields were valid last time they were validated.
*/ */
allFieldsValid: function() { allFieldsValid() {
const keys = Object.keys(this.state.fieldValid); const keys = Object.keys(this.state.fieldValid);
for (let i = 0; i < keys.length; ++i) { for (let i = 0; i < keys.length; ++i) {
if (!this.state.fieldValid[keys[i]]) { if (!this.state.fieldValid[keys[i]]) {
@ -209,7 +206,7 @@ export default createReactClass({
} }
} }
return true; return true;
}, }
findFirstInvalidField(fieldIDs) { findFirstInvalidField(fieldIDs) {
for (const fieldID of fieldIDs) { for (const fieldID of fieldIDs) {
@ -218,34 +215,34 @@ export default createReactClass({
} }
} }
return null; return null;
}, }
markFieldValid: function(fieldID, valid) { markFieldValid(fieldID, valid) {
const { fieldValid } = this.state; const { fieldValid } = this.state;
fieldValid[fieldID] = valid; fieldValid[fieldID] = valid;
this.setState({ this.setState({
fieldValid, fieldValid,
}); });
}, }
onEmailChange(ev) { onEmailChange = ev => {
this.setState({ this.setState({
email: ev.target.value, email: ev.target.value,
}); });
}, };
async onEmailValidate(fieldState) { onEmailValidate = async fieldState => {
const result = await this.validateEmailRules(fieldState); const result = await RegistrationForm.validateEmailRules(fieldState);
this.markFieldValid(FIELD_EMAIL, result.valid); this.markFieldValid(FIELD_EMAIL, result.valid);
return result; return result;
}, };
validateEmailRules: withValidation({ static validateEmailRules = withValidation({
description: () => _t("Use an email address to recover your account"), description: () => _t("Use an email address to recover your account"),
rules: [ rules: [
{ {
key: "required", key: "required",
test: function({ value, allowEmpty }) { test({ value, allowEmpty }) {
return allowEmpty || !this._authStepIsRequired('m.login.email.identity') || !!value; return allowEmpty || !this._authStepIsRequired('m.login.email.identity') || !!value;
}, },
invalid: () => _t("Enter email address (required on this homeserver)"), invalid: () => _t("Enter email address (required on this homeserver)"),
@ -256,31 +253,31 @@ export default createReactClass({
invalid: () => _t("Doesn't look like a valid email address"), invalid: () => _t("Doesn't look like a valid email address"),
}, },
], ],
}), });
onPasswordChange(ev) { onPasswordChange = ev => {
this.setState({ this.setState({
password: ev.target.value, password: ev.target.value,
}); });
}, };
onPasswordValidate(result) { onPasswordValidate = result => {
this.markFieldValid(FIELD_PASSWORD, result.valid); this.markFieldValid(FIELD_PASSWORD, result.valid);
}, };
onPasswordConfirmChange(ev) { onPasswordConfirmChange = ev => {
this.setState({ this.setState({
passwordConfirm: ev.target.value, passwordConfirm: ev.target.value,
}); });
}, };
async onPasswordConfirmValidate(fieldState) { onPasswordConfirmValidate = async fieldState => {
const result = await this.validatePasswordConfirmRules(fieldState); const result = await RegistrationForm.validatePasswordConfirmRules(fieldState);
this.markFieldValid(FIELD_PASSWORD_CONFIRM, result.valid); this.markFieldValid(FIELD_PASSWORD_CONFIRM, result.valid);
return result; return result;
}, };
validatePasswordConfirmRules: withValidation({ static validatePasswordConfirmRules = withValidation({
rules: [ rules: [
{ {
key: "required", key: "required",
@ -289,39 +286,39 @@ export default createReactClass({
}, },
{ {
key: "match", key: "match",
test: function({ value }) { test({ value }) {
return !value || value === this.state.password; return !value || value === this.state.password;
}, },
invalid: () => _t("Passwords don't match"), invalid: () => _t("Passwords don't match"),
}, },
], ],
}), });
onPhoneCountryChange(newVal) { onPhoneCountryChange = newVal => {
this.setState({ this.setState({
phoneCountry: newVal.iso2, phoneCountry: newVal.iso2,
phonePrefix: newVal.prefix, phonePrefix: newVal.prefix,
}); });
}, };
onPhoneNumberChange(ev) { onPhoneNumberChange = ev => {
this.setState({ this.setState({
phoneNumber: ev.target.value, phoneNumber: ev.target.value,
}); });
}, };
async onPhoneNumberValidate(fieldState) { onPhoneNumberValidate = async fieldState => {
const result = await this.validatePhoneNumberRules(fieldState); const result = await RegistrationForm.validatePhoneNumberRules(fieldState);
this.markFieldValid(FIELD_PHONE_NUMBER, result.valid); this.markFieldValid(FIELD_PHONE_NUMBER, result.valid);
return result; return result;
}, };
validatePhoneNumberRules: withValidation({ static validatePhoneNumberRules = withValidation({
description: () => _t("Other users can invite you to rooms using your contact details"), description: () => _t("Other users can invite you to rooms using your contact details"),
rules: [ rules: [
{ {
key: "required", key: "required",
test: function({ value, allowEmpty }) { test({ value, allowEmpty }) {
return allowEmpty || !this._authStepIsRequired('m.login.msisdn') || !!value; return allowEmpty || !this._authStepIsRequired('m.login.msisdn') || !!value;
}, },
invalid: () => _t("Enter phone number (required on this homeserver)"), invalid: () => _t("Enter phone number (required on this homeserver)"),
@ -332,21 +329,21 @@ export default createReactClass({
invalid: () => _t("Doesn't look like a valid phone number"), invalid: () => _t("Doesn't look like a valid phone number"),
}, },
], ],
}), });
onUsernameChange(ev) { onUsernameChange = ev => {
this.setState({ this.setState({
username: ev.target.value, username: ev.target.value,
}); });
}, };
async onUsernameValidate(fieldState) { onUsernameValidate = async fieldState => {
const result = await this.validateUsernameRules(fieldState); const result = await RegistrationForm.validateUsernameRules(fieldState);
this.markFieldValid(FIELD_USERNAME, result.valid); this.markFieldValid(FIELD_USERNAME, result.valid);
return result; return result;
}, };
validateUsernameRules: withValidation({ static validateUsernameRules = withValidation({
description: () => _t("Use lowercase letters, numbers, dashes and underscores only"), description: () => _t("Use lowercase letters, numbers, dashes and underscores only"),
rules: [ rules: [
{ {
@ -360,7 +357,7 @@ export default createReactClass({
invalid: () => _t("Some characters not allowed"), invalid: () => _t("Some characters not allowed"),
}, },
], ],
}), });
/** /**
* A step is required if all flows include that step. * A step is required if all flows include that step.
@ -372,7 +369,7 @@ export default createReactClass({
return this.props.flows.every((flow) => { return this.props.flows.every((flow) => {
return flow.stages.includes(step); return flow.stages.includes(step);
}); });
}, }
/** /**
* A step is used if any flows include that step. * A step is used if any flows include that step.
@ -384,7 +381,7 @@ export default createReactClass({
return this.props.flows.some((flow) => { return this.props.flows.some((flow) => {
return flow.stages.includes(step); return flow.stages.includes(step);
}); });
}, }
_showEmail() { _showEmail() {
const haveIs = Boolean(this.props.serverConfig.isUrl); const haveIs = Boolean(this.props.serverConfig.isUrl);
@ -395,7 +392,7 @@ export default createReactClass({
return false; return false;
} }
return true; return true;
}, }
_showPhoneNumber() { _showPhoneNumber() {
const threePidLogin = !SdkConfig.get().disable_3pid_login; const threePidLogin = !SdkConfig.get().disable_3pid_login;
@ -408,7 +405,7 @@ export default createReactClass({
return false; return false;
} }
return true; return true;
}, }
renderEmail() { renderEmail() {
if (!this._showEmail()) { if (!this._showEmail()) {
@ -426,7 +423,7 @@ export default createReactClass({
onChange={this.onEmailChange} onChange={this.onEmailChange}
onValidate={this.onEmailValidate} onValidate={this.onEmailValidate}
/>; />;
}, }
renderPassword() { renderPassword() {
return <PassphraseField return <PassphraseField
@ -437,7 +434,7 @@ export default createReactClass({
onChange={this.onPasswordChange} onChange={this.onPasswordChange}
onValidate={this.onPasswordValidate} onValidate={this.onPasswordValidate}
/>; />;
}, }
renderPasswordConfirm() { renderPasswordConfirm() {
const Field = sdk.getComponent('elements.Field'); const Field = sdk.getComponent('elements.Field');
@ -451,7 +448,7 @@ export default createReactClass({
onChange={this.onPasswordConfirmChange} onChange={this.onPasswordConfirmChange}
onValidate={this.onPasswordConfirmValidate} onValidate={this.onPasswordConfirmValidate}
/>; />;
}, }
renderPhoneNumber() { renderPhoneNumber() {
if (!this._showPhoneNumber()) { if (!this._showPhoneNumber()) {
@ -477,7 +474,7 @@ export default createReactClass({
onChange={this.onPhoneNumberChange} onChange={this.onPhoneNumberChange}
onValidate={this.onPhoneNumberValidate} onValidate={this.onPhoneNumberValidate}
/>; />;
}, }
renderUsername() { renderUsername() {
const Field = sdk.getComponent('elements.Field'); const Field = sdk.getComponent('elements.Field');
@ -491,9 +488,9 @@ export default createReactClass({
onChange={this.onUsernameChange} onChange={this.onUsernameChange}
onValidate={this.onUsernameValidate} onValidate={this.onUsernameValidate}
/>; />;
}, }
render: function() { render() {
let yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', { let yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', {
serverName: this.props.serverConfig.hsName, serverName: this.props.serverConfig.hsName,
}); });
@ -578,5 +575,5 @@ export default createReactClass({
</form> </form>
</div> </div>
); );
}, }
}); }

View file

@ -19,7 +19,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {EventStatus} from 'matrix-js-sdk'; import {EventStatus} from 'matrix-js-sdk';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
@ -37,10 +36,8 @@ function canCancel(eventStatus) {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
} }
export default createReactClass({ export default class MessageContextMenu extends React.Component {
displayName: 'MessageContextMenu', static propTypes = {
propTypes: {
/* the MatrixEvent associated with the context menu */ /* the MatrixEvent associated with the context menu */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
@ -52,28 +49,26 @@ export default createReactClass({
/* callback called when the menu is dismissed */ /* callback called when the menu is dismissed */
onFinished: PropTypes.func, onFinished: PropTypes.func,
}, };
getInitialState: function() { state = {
return { canRedact: false,
canRedact: false, canPin: false,
canPin: false, };
};
},
componentDidMount: function() { componentDidMount() {
MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions); MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
this._checkPermissions(); this._checkPermissions();
}, }
componentWillUnmount: function() { componentWillUnmount() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (cli) { if (cli) {
cli.removeListener('RoomMember.powerLevel', this._checkPermissions); cli.removeListener('RoomMember.powerLevel', this._checkPermissions);
} }
}, }
_checkPermissions: function() { _checkPermissions = () => {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.mxEvent.getRoomId()); const room = cli.getRoom(this.props.mxEvent.getRoomId());
@ -84,47 +79,47 @@ export default createReactClass({
if (!SettingsStore.getValue("feature_pinning")) canPin = false; if (!SettingsStore.getValue("feature_pinning")) canPin = false;
this.setState({canRedact, canPin}); this.setState({canRedact, canPin});
}, };
_isPinned: function() { _isPinned() {
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', ''); const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', '');
if (!pinnedEvent) return false; if (!pinnedEvent) return false;
const content = pinnedEvent.getContent(); const content = pinnedEvent.getContent();
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId()); return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
}, }
onResendClick: function() { onResendClick = () => {
Resend.resend(this.props.mxEvent); Resend.resend(this.props.mxEvent);
this.closeMenu(); this.closeMenu();
}, };
onResendEditClick: function() { onResendEditClick = () => {
Resend.resend(this.props.mxEvent.replacingEvent()); Resend.resend(this.props.mxEvent.replacingEvent());
this.closeMenu(); this.closeMenu();
}, };
onResendRedactionClick: function() { onResendRedactionClick = () => {
Resend.resend(this.props.mxEvent.localRedactionEvent()); Resend.resend(this.props.mxEvent.localRedactionEvent());
this.closeMenu(); this.closeMenu();
}, };
onResendReactionsClick: function() { onResendReactionsClick = () => {
for (const reaction of this._getUnsentReactions()) { for (const reaction of this._getUnsentReactions()) {
Resend.resend(reaction); Resend.resend(reaction);
} }
this.closeMenu(); this.closeMenu();
}, };
onReportEventClick: function() { onReportEventClick = () => {
const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog"); const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog");
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, { Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
mxEvent: this.props.mxEvent, mxEvent: this.props.mxEvent,
}, 'mx_Dialog_reportEvent'); }, 'mx_Dialog_reportEvent');
this.closeMenu(); this.closeMenu();
}, };
onViewSourceClick: function() { onViewSourceClick = () => {
const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent; const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent;
const ViewSource = sdk.getComponent('structures.ViewSource'); const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Event Source', '', ViewSource, { Modal.createTrackedDialog('View Event Source', '', ViewSource, {
@ -133,9 +128,9 @@ export default createReactClass({
content: ev.event, content: ev.event,
}, 'mx_Dialog_viewsource'); }, 'mx_Dialog_viewsource');
this.closeMenu(); this.closeMenu();
}, };
onViewClearSourceClick: function() { onViewClearSourceClick = () => {
const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent; const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent;
const ViewSource = sdk.getComponent('structures.ViewSource'); const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, { Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, {
@ -145,9 +140,9 @@ export default createReactClass({
content: ev._clearEvent, content: ev._clearEvent,
}, 'mx_Dialog_viewsource'); }, 'mx_Dialog_viewsource');
this.closeMenu(); this.closeMenu();
}, };
onRedactClick: function() { onRedactClick = () => {
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog"); const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, { Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
onFinished: async (proceed) => { onFinished: async (proceed) => {
@ -176,9 +171,9 @@ export default createReactClass({
}, },
}, 'mx_Dialog_confirmredact'); }, 'mx_Dialog_confirmredact');
this.closeMenu(); this.closeMenu();
}, };
onCancelSendClick: function() { onCancelSendClick = () => {
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
const editEvent = mxEvent.replacingEvent(); const editEvent = mxEvent.replacingEvent();
const redactEvent = mxEvent.localRedactionEvent(); const redactEvent = mxEvent.localRedactionEvent();
@ -199,17 +194,17 @@ export default createReactClass({
Resend.removeFromQueue(this.props.mxEvent); Resend.removeFromQueue(this.props.mxEvent);
} }
this.closeMenu(); this.closeMenu();
}, };
onForwardClick: function() { onForwardClick = () => {
dis.dispatch({ dis.dispatch({
action: 'forward_event', action: 'forward_event',
event: this.props.mxEvent, event: this.props.mxEvent,
}); });
this.closeMenu(); this.closeMenu();
}, };
onPinClick: function() { onPinClick = () => {
MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '') MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '')
.catch((e) => { .catch((e) => {
// Intercept the Event Not Found error and fall through the promise chain with no event. // Intercept the Event Not Found error and fall through the promise chain with no event.
@ -230,28 +225,28 @@ export default createReactClass({
cli.sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, ''); cli.sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, '');
}); });
this.closeMenu(); this.closeMenu();
}, };
closeMenu: function() { closeMenu = () => {
if (this.props.onFinished) this.props.onFinished(); if (this.props.onFinished) this.props.onFinished();
}, };
onUnhidePreviewClick: function() { onUnhidePreviewClick = () => {
if (this.props.eventTileOps) { if (this.props.eventTileOps) {
this.props.eventTileOps.unhideWidget(); this.props.eventTileOps.unhideWidget();
} }
this.closeMenu(); this.closeMenu();
}, };
onQuoteClick: function() { onQuoteClick = () => {
dis.dispatch({ dis.dispatch({
action: 'quote', action: 'quote',
event: this.props.mxEvent, event: this.props.mxEvent,
}); });
this.closeMenu(); this.closeMenu();
}, };
onPermalinkClick: function(e: Event) { onPermalinkClick = (e: Event) => {
e.preventDefault(); e.preventDefault();
const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, { Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
@ -259,12 +254,12 @@ export default createReactClass({
permalinkCreator: this.props.permalinkCreator, permalinkCreator: this.props.permalinkCreator,
}); });
this.closeMenu(); this.closeMenu();
}, };
onCollapseReplyThreadClick: function() { onCollapseReplyThreadClick = () => {
this.props.collapseReplyThread(); this.props.collapseReplyThread();
this.closeMenu(); this.closeMenu();
}, };
_getReactions(filter) { _getReactions(filter) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -277,17 +272,17 @@ export default createReactClass({
relation.event_id === eventId && relation.event_id === eventId &&
filter(e); filter(e);
}); });
}, }
_getPendingReactions() { _getPendingReactions() {
return this._getReactions(e => canCancel(e.status)); return this._getReactions(e => canCancel(e.status));
}, }
_getUnsentReactions() { _getUnsentReactions() {
return this._getReactions(e => e.status === EventStatus.NOT_SENT); return this._getReactions(e => e.status === EventStatus.NOT_SENT);
}, }
render: function() { render() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const me = cli.getUserId(); const me = cli.getUserId();
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
@ -489,5 +484,5 @@ export default createReactClass({
{ reportEventButton } { reportEventButton }
</div> </div>
); );
}, }
}); }

View file

@ -19,7 +19,6 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -45,10 +44,8 @@ const addressTypeName = {
}; };
export default createReactClass({ export default class AddressPickerDialog extends React.Component {
displayName: "AddressPickerDialog", static propTypes = {
propTypes: {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
description: PropTypes.node, description: PropTypes.node,
// Extra node inserted after picker input, dropdown and errors // Extra node inserted after picker input, dropdown and errors
@ -66,26 +63,28 @@ export default createReactClass({
// Whether the current user should be included in the addresses returned. Only // Whether the current user should be included in the addresses returned. Only
// applicable when pickerType is `user`. Default: false. // applicable when pickerType is `user`. Default: false.
includeSelf: PropTypes.bool, includeSelf: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { value: "",
value: "", focus: true,
focus: true, validAddressTypes: addressTypes,
validAddressTypes: addressTypes, pickerType: 'user',
pickerType: 'user', includeSelf: false,
includeSelf: false, };
};
}, constructor(props) {
super(props);
this._textinput = createRef();
getInitialState: function() {
let validAddressTypes = this.props.validAddressTypes; let validAddressTypes = this.props.validAddressTypes;
// Remove email from validAddressTypes if no IS is configured. It may be added at a later stage by the user // Remove email from validAddressTypes if no IS is configured. It may be added at a later stage by the user
if (!MatrixClientPeg.get().getIdentityServerUrl() && validAddressTypes.includes("email")) { if (!MatrixClientPeg.get().getIdentityServerUrl() && validAddressTypes.includes("email")) {
validAddressTypes = validAddressTypes.filter(type => type !== "email"); validAddressTypes = validAddressTypes.filter(type => type !== "email");
} }
return { this.state = {
// Whether to show an error message because of an invalid address // Whether to show an error message because of an invalid address
invalidAddressError: false, invalidAddressError: false,
// List of UserAddressType objects representing // List of UserAddressType objects representing
@ -106,19 +105,14 @@ export default createReactClass({
// dialog is open and represents the supported list of address types at this time. // dialog is open and represents the supported list of address types at this time.
validAddressTypes, validAddressTypes,
}; };
}, }
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs componentDidMount() {
UNSAFE_componentWillMount: function() {
this._textinput = createRef();
},
componentDidMount: function() {
if (this.props.focus) { if (this.props.focus) {
// Set the cursor at the end of the text input // Set the cursor at the end of the text input
this._textinput.current.value = this.props.value; this._textinput.current.value = this.props.value;
} }
}, }
getPlaceholder() { getPlaceholder() {
const { placeholder } = this.props; const { placeholder } = this.props;
@ -127,9 +121,9 @@ export default createReactClass({
} }
// Otherwise it's a function, as checked by prop types. // Otherwise it's a function, as checked by prop types.
return placeholder(this.state.validAddressTypes); return placeholder(this.state.validAddressTypes);
}, }
onButtonClick: function() { onButtonClick = () => {
let selectedList = this.state.selectedList.slice(); let selectedList = this.state.selectedList.slice();
// Check the text input field to see if user has an unconverted address // Check the text input field to see if user has an unconverted address
// If there is and it's valid add it to the local selectedList // If there is and it's valid add it to the local selectedList
@ -138,13 +132,13 @@ export default createReactClass({
if (selectedList === null) return; if (selectedList === null) return;
} }
this.props.onFinished(true, selectedList); this.props.onFinished(true, selectedList);
}, };
onCancel: function() { onCancel = () => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
onKeyDown: function(e) { onKeyDown = e => {
const textInput = this._textinput.current ? this._textinput.current.value : undefined; const textInput = this._textinput.current ? this._textinput.current.value : undefined;
if (e.key === Key.ESCAPE) { if (e.key === Key.ESCAPE) {
@ -181,9 +175,9 @@ export default createReactClass({
e.preventDefault(); e.preventDefault();
this._addAddressesToList([textInput]); this._addAddressesToList([textInput]);
} }
}, };
onQueryChanged: function(ev) { onQueryChanged = ev => {
const query = ev.target.value; const query = ev.target.value;
if (this.queryChangedDebouncer) { if (this.queryChangedDebouncer) {
clearTimeout(this.queryChangedDebouncer); clearTimeout(this.queryChangedDebouncer);
@ -216,28 +210,24 @@ export default createReactClass({
searchError: null, searchError: null,
}); });
} }
}, };
onDismissed: function(index) { onDismissed = index => () => {
return () => { const selectedList = this.state.selectedList.slice();
const selectedList = this.state.selectedList.slice(); selectedList.splice(index, 1);
selectedList.splice(index, 1); this.setState({
this.setState({ selectedList,
selectedList, suggestedList: [],
suggestedList: [], query: "",
query: "", });
}); if (this._cancelThreepidLookup) this._cancelThreepidLookup();
if (this._cancelThreepidLookup) this._cancelThreepidLookup(); };
};
},
onClick: function(index) { onClick = index => () => {
return () => { this.onSelected(index);
this.onSelected(index); };
};
},
onSelected: function(index) { onSelected = index => {
const selectedList = this.state.selectedList.slice(); const selectedList = this.state.selectedList.slice();
selectedList.push(this._getFilteredSuggestions()[index]); selectedList.push(this._getFilteredSuggestions()[index]);
this.setState({ this.setState({
@ -246,9 +236,9 @@ export default createReactClass({
query: "", query: "",
}); });
if (this._cancelThreepidLookup) this._cancelThreepidLookup(); if (this._cancelThreepidLookup) this._cancelThreepidLookup();
}, };
_doNaiveGroupSearch: function(query) { _doNaiveGroupSearch(query) {
const lowerCaseQuery = query.toLowerCase(); const lowerCaseQuery = query.toLowerCase();
this.setState({ this.setState({
busy: true, busy: true,
@ -280,9 +270,9 @@ export default createReactClass({
busy: false, busy: false,
}); });
}); });
}, }
_doNaiveGroupRoomSearch: function(query) { _doNaiveGroupRoomSearch(query) {
const lowerCaseQuery = query.toLowerCase(); const lowerCaseQuery = query.toLowerCase();
const results = []; const results = [];
GroupStore.getGroupRooms(this.props.groupId).forEach((r) => { GroupStore.getGroupRooms(this.props.groupId).forEach((r) => {
@ -302,9 +292,9 @@ export default createReactClass({
this.setState({ this.setState({
busy: false, busy: false,
}); });
}, }
_doRoomSearch: function(query) { _doRoomSearch(query) {
const lowerCaseQuery = query.toLowerCase(); const lowerCaseQuery = query.toLowerCase();
const rooms = MatrixClientPeg.get().getRooms(); const rooms = MatrixClientPeg.get().getRooms();
const results = []; const results = [];
@ -359,9 +349,9 @@ export default createReactClass({
this.setState({ this.setState({
busy: false, busy: false,
}); });
}, }
_doUserDirectorySearch: function(query) { _doUserDirectorySearch(query) {
this.setState({ this.setState({
busy: true, busy: true,
query, query,
@ -393,9 +383,9 @@ export default createReactClass({
busy: false, busy: false,
}); });
}); });
}, }
_doLocalSearch: function(query) { _doLocalSearch(query) {
this.setState({ this.setState({
query, query,
searchError: null, searchError: null,
@ -417,9 +407,9 @@ export default createReactClass({
}); });
}); });
this._processResults(results, query); this._processResults(results, query);
}, }
_processResults: function(results, query) { _processResults(results, query) {
const suggestedList = []; const suggestedList = [];
results.forEach((result) => { results.forEach((result) => {
if (result.room_id) { if (result.room_id) {
@ -485,9 +475,9 @@ export default createReactClass({
}, () => { }, () => {
if (this.addressSelector) this.addressSelector.moveSelectionTop(); if (this.addressSelector) this.addressSelector.moveSelectionTop();
}); });
}, }
_addAddressesToList: function(addressTexts) { _addAddressesToList(addressTexts) {
const selectedList = this.state.selectedList.slice(); const selectedList = this.state.selectedList.slice();
let hasError = false; let hasError = false;
@ -529,9 +519,9 @@ export default createReactClass({
}); });
if (this._cancelThreepidLookup) this._cancelThreepidLookup(); if (this._cancelThreepidLookup) this._cancelThreepidLookup();
return hasError ? null : selectedList; return hasError ? null : selectedList;
}, }
_lookupThreepid: async function(medium, address) { async _lookupThreepid(medium, address) {
let cancelled = false; let cancelled = false;
// Note that we can't safely remove this after we're done // Note that we can't safely remove this after we're done
// because we don't know that it's the same one, so we just // because we don't know that it's the same one, so we just
@ -577,9 +567,9 @@ export default createReactClass({
searchError: _t('Something went wrong!'), searchError: _t('Something went wrong!'),
}); });
} }
}, }
_getFilteredSuggestions: function() { _getFilteredSuggestions() {
// map addressType => set of addresses to avoid O(n*m) operation // map addressType => set of addresses to avoid O(n*m) operation
const selectedAddresses = {}; const selectedAddresses = {};
this.state.selectedList.forEach(({address, addressType}) => { this.state.selectedList.forEach(({address, addressType}) => {
@ -591,17 +581,17 @@ export default createReactClass({
return this.state.suggestedList.filter(({address, addressType}) => { return this.state.suggestedList.filter(({address, addressType}) => {
return !(selectedAddresses[addressType] && selectedAddresses[addressType].has(address)); return !(selectedAddresses[addressType] && selectedAddresses[addressType].has(address));
}); });
}, }
_onPaste: function(e) { _onPaste = e => {
// Prevent the text being pasted into the textarea // Prevent the text being pasted into the textarea
e.preventDefault(); e.preventDefault();
const text = e.clipboardData.getData("text"); const text = e.clipboardData.getData("text");
// Process it as a list of addresses to add instead // Process it as a list of addresses to add instead
this._addAddressesToList(text.split(/[\s,]+/)); this._addAddressesToList(text.split(/[\s,]+/));
}, };
onUseDefaultIdentityServerClick(e) { onUseDefaultIdentityServerClick = e => {
e.preventDefault(); e.preventDefault();
// Update the IS in account data. Actually using it may trigger terms. // Update the IS in account data. Actually using it may trigger terms.
@ -612,15 +602,15 @@ export default createReactClass({
const { validAddressTypes } = this.state; const { validAddressTypes } = this.state;
validAddressTypes.push('email'); validAddressTypes.push('email');
this.setState({ validAddressTypes }); this.setState({ validAddressTypes });
}, };
onManageSettingsClick(e) { onManageSettingsClick = e => {
e.preventDefault(); e.preventDefault();
dis.fire(Action.ViewUserSettings); dis.fire(Action.ViewUserSettings);
this.onCancel(); this.onCancel();
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AddressSelector = sdk.getComponent("elements.AddressSelector"); const AddressSelector = sdk.getComponent("elements.AddressSelector");
@ -738,5 +728,5 @@ export default createReactClass({
onCancel={this.onCancel} /> onCancel={this.onCancel} />
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -16,37 +16,36 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel"; import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({ export default class AskInviteAnywayDialog extends React.Component {
propTypes: { static propTypes = {
unknownProfileUsers: PropTypes.array.isRequired, // [ {userId, errorText}... ] unknownProfileUsers: PropTypes.array.isRequired, // [ {userId, errorText}... ]
onInviteAnyways: PropTypes.func.isRequired, onInviteAnyways: PropTypes.func.isRequired,
onGiveUp: PropTypes.func.isRequired, onGiveUp: PropTypes.func.isRequired,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
_onInviteClicked: function() { _onInviteClicked = () => {
this.props.onInviteAnyways(); this.props.onInviteAnyways();
this.props.onFinished(true); this.props.onFinished(true);
}, };
_onInviteNeverWarnClicked: function() { _onInviteNeverWarnClicked = () => {
SettingsStore.setValue("promptBeforeInviteUnknownUsers", null, SettingLevel.ACCOUNT, false); SettingsStore.setValue("promptBeforeInviteUnknownUsers", null, SettingLevel.ACCOUNT, false);
this.props.onInviteAnyways(); this.props.onInviteAnyways();
this.props.onFinished(true); this.props.onFinished(true);
}, };
_onGiveUpClicked: function() { _onGiveUpClicked = () => {
this.props.onGiveUp(); this.props.onGiveUp();
this.props.onFinished(false); this.props.onFinished(false);
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const errorList = this.props.unknownProfileUsers const errorList = this.props.unknownProfileUsers
@ -78,5 +77,5 @@ export default createReactClass({
</div> </div>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import FocusLock from 'react-focus-lock'; import FocusLock from 'react-focus-lock';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
@ -34,10 +33,8 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
* Includes a div for the title, and a keypress handler which cancels the * Includes a div for the title, and a keypress handler which cancels the
* dialog on escape. * dialog on escape.
*/ */
export default createReactClass({ export default class BaseDialog extends React.Component {
displayName: 'BaseDialog', static propTypes = {
propTypes: {
// onFinished callback to call when Escape is pressed // onFinished callback to call when Escape is pressed
// Take a boolean which is true if the dialog was dismissed // Take a boolean which is true if the dialog was dismissed
// with a positive / confirm action or false if it was // with a positive / confirm action or false if it was
@ -81,21 +78,20 @@ export default createReactClass({
PropTypes.object, PropTypes.object,
PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.string),
]), ]),
}, };
getDefaultProps: function() { static defaultProps = {
return { hasCancel: true,
hasCancel: true, fixedWidth: true,
fixedWidth: true, };
};
}, constructor(props) {
super(props);
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount() {
this._matrixClient = MatrixClientPeg.get(); this._matrixClient = MatrixClientPeg.get();
}, }
_onKeyDown: function(e) { _onKeyDown = (e) => {
if (this.props.onKeyDown) { if (this.props.onKeyDown) {
this.props.onKeyDown(e); this.props.onKeyDown(e);
} }
@ -104,13 +100,13 @@ export default createReactClass({
e.preventDefault(); e.preventDefault();
this.props.onFinished(false); this.props.onFinished(false);
} }
}, };
_onCancelClick: function(e) { _onCancelClick = (e) => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
render: function() { render() {
let cancelButton; let cancelButton;
if (this.props.hasCancel) { if (this.props.hasCancel) {
cancelButton = ( cancelButton = (
@ -161,5 +157,5 @@ export default createReactClass({
</FocusLock> </FocusLock>
</MatrixClientContext.Provider> </MatrixClientContext.Provider>
); );
}, }
}); }

View file

@ -15,17 +15,14 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
/* /*
* A dialog for confirming a redaction. * A dialog for confirming a redaction.
*/ */
export default createReactClass({ export default class ConfirmRedactDialog extends React.Component {
displayName: 'ConfirmRedactDialog', render() {
render: function() {
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog'); const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
return ( return (
<QuestionDialog onFinished={this.props.onFinished} <QuestionDialog onFinished={this.props.onFinished}
@ -36,5 +33,5 @@ export default createReactClass({
button={_t("Remove")}> button={_t("Remove")}>
</QuestionDialog> </QuestionDialog>
); );
}, }
}); }

View file

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk'; import { MatrixClient } from 'matrix-js-sdk';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -30,9 +29,8 @@ import { GroupMemberType } from '../../../groups';
* to make it obvious what is going to happen. * to make it obvious what is going to happen.
* Also tweaks the style for 'dangerous' actions (albeit only with colour) * Also tweaks the style for 'dangerous' actions (albeit only with colour)
*/ */
export default createReactClass({ export default class ConfirmUserActionDialog extends React.Component {
displayName: 'ConfirmUserActionDialog', static propTypes = {
propTypes: {
// matrix-js-sdk (room) member object. Supply either this or 'groupMember' // matrix-js-sdk (room) member object. Supply either this or 'groupMember'
member: PropTypes.object, member: PropTypes.object,
// group member object. Supply either this or 'member' // group member object. Supply either this or 'member'
@ -48,35 +46,36 @@ export default createReactClass({
askReason: PropTypes.bool, askReason: PropTypes.bool,
danger: PropTypes.bool, danger: PropTypes.bool,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
getDefaultProps: () => ({ static defaultProps = {
danger: false, danger: false,
askReason: false, askReason: false,
}), };
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._reasonField = null; this._reasonField = null;
}, }
onOk: function() { onOk = () => {
let reason; let reason;
if (this._reasonField) { if (this._reasonField) {
reason = this._reasonField.value; reason = this._reasonField.value;
} }
this.props.onFinished(true, reason); this.props.onFinished(true, reason);
}, };
onCancel: function() { onCancel = () => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
_collectReasonField: function(e) { _collectReasonField = e => {
this._reasonField = e; this._reasonField = e;
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
@ -134,5 +133,5 @@ export default createReactClass({
onCancel={this.onCancel} /> onCancel={this.onCancel} />
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -15,46 +15,42 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
export default createReactClass({ export default class CreateGroupDialog extends React.Component {
displayName: 'CreateGroupDialog', static propTypes = {
propTypes: {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
getInitialState: function() { state = {
return { groupName: '',
groupName: '', groupId: '',
groupId: '', groupError: null,
groupError: null, creating: false,
creating: false, createError: null,
createError: null, };
};
},
_onGroupNameChange: function(e) { _onGroupNameChange = e => {
this.setState({ this.setState({
groupName: e.target.value, groupName: e.target.value,
}); });
}, };
_onGroupIdChange: function(e) { _onGroupIdChange = e => {
this.setState({ this.setState({
groupId: e.target.value, groupId: e.target.value,
}); });
}, };
_onGroupIdBlur: function(e) { _onGroupIdBlur = e => {
this._checkGroupId(); this._checkGroupId();
}, };
_checkGroupId: function(e) { _checkGroupId(e) {
let error = null; let error = null;
if (!this.state.groupId) { if (!this.state.groupId) {
error = _t("Community IDs cannot be empty."); error = _t("Community IDs cannot be empty.");
@ -67,9 +63,9 @@ export default createReactClass({
createError: null, createError: null,
}); });
return error; return error;
}, }
_onFormSubmit: function(e) { _onFormSubmit = e => {
e.preventDefault(); e.preventDefault();
if (this._checkGroupId()) return; if (this._checkGroupId()) return;
@ -94,13 +90,13 @@ export default createReactClass({
}).finally(() => { }).finally(() => {
this.setState({creating: false}); this.setState({creating: false});
}); });
}, };
_onCancel: function() { _onCancel = () => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const Spinner = sdk.getComponent('elements.Spinner'); const Spinner = sdk.getComponent('elements.Spinner');
@ -171,5 +167,5 @@ export default createReactClass({
</form> </form>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
@ -28,16 +27,17 @@ import {privateShouldBeEncrypted} from "../../../createRoom";
import TagOrderStore from "../../../stores/TagOrderStore"; import TagOrderStore from "../../../stores/TagOrderStore";
import GroupStore from "../../../stores/GroupStore"; import GroupStore from "../../../stores/GroupStore";
export default createReactClass({ export default class CreateRoomDialog extends React.Component {
displayName: 'CreateRoomDialog', static propTypes = {
propTypes: {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
defaultPublic: PropTypes.bool, defaultPublic: PropTypes.bool,
}, };
constructor(props) {
super(props);
getInitialState() {
const config = SdkConfig.get(); const config = SdkConfig.get();
return { this.state = {
isPublic: this.props.defaultPublic || false, isPublic: this.props.defaultPublic || false,
isEncrypted: privateShouldBeEncrypted(), isEncrypted: privateShouldBeEncrypted(),
name: "", name: "",
@ -47,7 +47,7 @@ export default createReactClass({
noFederate: config.default_federate === false, noFederate: config.default_federate === false,
nameIsValid: false, nameIsValid: false,
}; };
}, }
_roomCreateOptions() { _roomCreateOptions() {
const opts = {}; const opts = {};
@ -77,27 +77,27 @@ export default createReactClass({
} }
return opts; return opts;
}, }
componentDidMount() { componentDidMount() {
this._detailsRef.addEventListener("toggle", this.onDetailsToggled); this._detailsRef.addEventListener("toggle", this.onDetailsToggled);
// move focus to first field when showing dialog // move focus to first field when showing dialog
this._nameFieldRef.focus(); this._nameFieldRef.focus();
}, }
componentWillUnmount() { componentWillUnmount() {
this._detailsRef.removeEventListener("toggle", this.onDetailsToggled); this._detailsRef.removeEventListener("toggle", this.onDetailsToggled);
}, }
_onKeyDown: function(event) { _onKeyDown = event => {
if (event.key === Key.ENTER) { if (event.key === Key.ENTER) {
this.onOk(); this.onOk();
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
} }
}, };
onOk: async function() { onOk = async () => {
const activeElement = document.activeElement; const activeElement = document.activeElement;
if (activeElement) { if (activeElement) {
activeElement.blur(); activeElement.blur();
@ -123,51 +123,51 @@ export default createReactClass({
field.validate({ allowEmpty: false, focused: true }); field.validate({ allowEmpty: false, focused: true });
} }
} }
}, };
onCancel: function() { onCancel = () => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
onNameChange(ev) { onNameChange = ev => {
this.setState({name: ev.target.value}); this.setState({name: ev.target.value});
}, };
onTopicChange(ev) { onTopicChange = ev => {
this.setState({topic: ev.target.value}); this.setState({topic: ev.target.value});
}, };
onPublicChange(isPublic) { onPublicChange = isPublic => {
this.setState({isPublic}); this.setState({isPublic});
}, };
onEncryptedChange(isEncrypted) { onEncryptedChange = isEncrypted => {
this.setState({isEncrypted}); this.setState({isEncrypted});
}, };
onAliasChange(alias) { onAliasChange = alias => {
this.setState({alias}); this.setState({alias});
}, };
onDetailsToggled(ev) { onDetailsToggled = ev => {
this.setState({detailsOpen: ev.target.open}); this.setState({detailsOpen: ev.target.open});
}, };
onNoFederateChange(noFederate) { onNoFederateChange = noFederate => {
this.setState({noFederate}); this.setState({noFederate});
}, };
collectDetailsRef(ref) { collectDetailsRef = ref => {
this._detailsRef = ref; this._detailsRef = ref;
}, };
async onNameValidate(fieldState) { onNameValidate = async fieldState => {
const result = await this._validateRoomName(fieldState); const result = await CreateRoomDialog._validateRoomName(fieldState);
this.setState({nameIsValid: result.valid}); this.setState({nameIsValid: result.valid});
return result; return result;
}, };
_validateRoomName: withValidation({ static _validateRoomName = withValidation({
rules: [ rules: [
{ {
key: "required", key: "required",
@ -175,9 +175,9 @@ export default createReactClass({
invalid: () => _t("Please enter a name for the room"), invalid: () => _t("Please enter a name for the room"),
}, },
], ],
}), });
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Field = sdk.getComponent('views.elements.Field'); const Field = sdk.getComponent('views.elements.Field');
@ -275,5 +275,5 @@ export default createReactClass({
onCancel={this.onCancel} /> onCancel={this.onCancel} />
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -26,14 +26,12 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
export default createReactClass({ export default class ErrorDialog extends React.Component {
displayName: 'ErrorDialog', static propTypes = {
propTypes: {
title: PropTypes.string, title: PropTypes.string,
description: PropTypes.oneOfType([ description: PropTypes.oneOfType([
PropTypes.element, PropTypes.element,
@ -43,18 +41,16 @@ export default createReactClass({
focus: PropTypes.bool, focus: PropTypes.bool,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
headerImage: PropTypes.string, headerImage: PropTypes.string,
}, };
getDefaultProps: function() { static defaultProps = {
return { focus: true,
focus: true, title: null,
title: null, description: null,
description: null, button: null,
button: null, };
};
},
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return ( return (
<BaseDialog <BaseDialog
@ -74,5 +70,5 @@ export default createReactClass({
</div> </div>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -17,15 +17,13 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import classNames from "classnames"; import classNames from "classnames";
export default createReactClass({ export default class InfoDialog extends React.Component {
displayName: 'InfoDialog', static propTypes = {
propTypes: {
className: PropTypes.string, className: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
description: PropTypes.node, description: PropTypes.node,
@ -33,21 +31,19 @@ export default createReactClass({
onFinished: PropTypes.func, onFinished: PropTypes.func,
hasCloseButton: PropTypes.bool, hasCloseButton: PropTypes.bool,
onKeyDown: PropTypes.func, onKeyDown: PropTypes.func,
}, };
getDefaultProps: function() { static defaultProps = {
return { title: '',
title: '', description: '',
description: '', hasCloseButton: false,
hasCloseButton: false, };
};
},
onFinished: function() { onFinished = () => {
this.props.onFinished(); this.props.onFinished();
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return ( return (
@ -69,5 +65,5 @@ export default createReactClass({
</DialogButtons> </DialogButtons>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -27,10 +26,8 @@ import AccessibleButton from '../elements/AccessibleButton';
import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth"; import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth";
import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents"; import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents";
export default createReactClass({ export default class InteractiveAuthDialog extends React.Component {
displayName: 'InteractiveAuthDialog', static propTypes = {
propTypes: {
// matrix client to use for UI auth requests // matrix client to use for UI auth requests
matrixClient: PropTypes.object.isRequired, matrixClient: PropTypes.object.isRequired,
@ -70,19 +67,17 @@ export default createReactClass({
// //
// Default is defined in _getDefaultDialogAesthetics() // Default is defined in _getDefaultDialogAesthetics()
aestheticsForStagePhases: PropTypes.object, aestheticsForStagePhases: PropTypes.object,
}, };
getInitialState: function() { state = {
return { authError: null,
authError: null,
// See _onUpdateStagePhase() // See _onUpdateStagePhase()
uiaStage: null, uiaStage: null,
uiaStagePhase: null, uiaStagePhase: null,
}; };
},
_getDefaultDialogAesthetics: function() { _getDefaultDialogAesthetics() {
const ssoAesthetics = { const ssoAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: { [SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"), title: _t("Use Single Sign On to continue"),
@ -102,9 +97,9 @@ export default createReactClass({
[SSOAuthEntry.LOGIN_TYPE]: ssoAesthetics, [SSOAuthEntry.LOGIN_TYPE]: ssoAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: ssoAesthetics, [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: ssoAesthetics,
}; };
}, }
_onAuthFinished: function(success, result) { _onAuthFinished = (success, result) => {
if (success) { if (success) {
this.props.onFinished(true, result); this.props.onFinished(true, result);
} else { } else {
@ -116,18 +111,18 @@ export default createReactClass({
}); });
} }
} }
}, };
_onUpdateStagePhase: function(newStage, newPhase) { _onUpdateStagePhase = (newStage, newPhase) => {
// We copy the stage and stage phase params into state for title selection in render() // We copy the stage and stage phase params into state for title selection in render()
this.setState({uiaStage: newStage, uiaStagePhase: newPhase}); this.setState({uiaStage: newStage, uiaStagePhase: newPhase});
}, };
_onDismissClick: function() { _onDismissClick = () => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
render: function() { render() {
const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth"); const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
@ -190,5 +185,5 @@ export default createReactClass({
{ content } { content }
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -16,14 +16,12 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
export default createReactClass({ export default class QuestionDialog extends React.Component {
displayName: 'QuestionDialog', static propTypes = {
propTypes: {
title: PropTypes.string, title: PropTypes.string,
description: PropTypes.node, description: PropTypes.node,
extraButtons: PropTypes.node, extraButtons: PropTypes.node,
@ -34,29 +32,27 @@ export default createReactClass({
headerImage: PropTypes.string, headerImage: PropTypes.string,
quitOnly: PropTypes.bool, // quitOnly doesn't show the cancel button just the quit [x]. quitOnly: PropTypes.bool, // quitOnly doesn't show the cancel button just the quit [x].
fixedWidth: PropTypes.bool, fixedWidth: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { title: "",
title: "", description: "",
description: "", extraButtons: null,
extraButtons: null, focus: true,
focus: true, hasCancelButton: true,
hasCancelButton: true, danger: false,
danger: false, quitOnly: false,
quitOnly: false, };
};
},
onOk: function() { onOk = () => {
this.props.onFinished(true); this.props.onFinished(true);
}, };
onCancel: function() { onCancel = () => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
let primaryButtonClass = ""; let primaryButtonClass = "";
@ -88,5 +84,5 @@ export default createReactClass({
</DialogButtons> </DialogButtons>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -15,38 +15,33 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
export default createReactClass({ export default class RoomUpgradeDialog extends React.Component {
displayName: 'RoomUpgradeDialog', static propTypes = {
propTypes: {
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
componentDidMount: async function() { state = {
busy: true,
};
async componentDidMount() {
const recommended = await this.props.room.getRecommendedVersion(); const recommended = await this.props.room.getRecommendedVersion();
this._targetVersion = recommended.version; this._targetVersion = recommended.version;
this.setState({busy: false}); this.setState({busy: false});
}, }
getInitialState: function() { _onCancelClick = () => {
return {
busy: true,
};
},
_onCancelClick: function() {
this.props.onFinished(false); this.props.onFinished(false);
}, };
_onUpgradeClick: function() { _onUpgradeClick = () => {
this.setState({busy: true}); this.setState({busy: true});
MatrixClientPeg.get().upgradeRoom(this.props.room.roomId, this._targetVersion).then(() => { MatrixClientPeg.get().upgradeRoom(this.props.room.roomId, this._targetVersion).then(() => {
this.props.onFinished(true); this.props.onFinished(true);
@ -59,9 +54,9 @@ export default createReactClass({
}).finally(() => { }).finally(() => {
this.setState({busy: false}); this.setState({busy: false});
}); });
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Spinner = sdk.getComponent('views.elements.Spinner'); const Spinner = sdk.getComponent('views.elements.Spinner');
@ -106,5 +101,5 @@ export default createReactClass({
{buttons} {buttons}
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
@ -25,20 +24,18 @@ import Modal from '../../../Modal';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
export default createReactClass({ export default class SessionRestoreErrorDialog extends React.Component {
displayName: 'SessionRestoreErrorDialog', static propTypes = {
propTypes: {
error: PropTypes.string.isRequired, error: PropTypes.string.isRequired,
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
_sendBugReport: function() { _sendBugReport = () => {
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog"); const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {}); Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {});
}, };
_onClearStorageClick: function() { _onClearStorageClick = () => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Session Restore Confirm Logout', '', QuestionDialog, { Modal.createTrackedDialog('Session Restore Confirm Logout', '', QuestionDialog, {
title: _t("Sign out"), title: _t("Sign out"),
@ -48,15 +45,15 @@ export default createReactClass({
danger: true, danger: true,
onFinished: this.props.onFinished, onFinished: this.props.onFinished,
}); });
}, };
_onRefreshClick: function() { _onRefreshClick = () => {
// Is this likely to help? Probably not, but giving only one button // Is this likely to help? Probably not, but giving only one button
// that clears your storage seems awful. // that clears your storage seems awful.
window.location.reload(true); window.location.reload(true);
}, };
render: function() { render() {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@ -110,5 +107,5 @@ export default createReactClass({
{ dialogButtons } { dialogButtons }
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import * as Email from '../../../email'; import * as Email from '../../../email';
@ -30,26 +29,23 @@ import Modal from '../../../Modal';
* *
* On success, `onFinished(true)` is called. * On success, `onFinished(true)` is called.
*/ */
export default createReactClass({ export default class SetEmailDialog extends React.Component {
displayName: 'SetEmailDialog', static propTypes = {
propTypes: {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
getInitialState: function() { state = {
return { emailAddress: '',
emailAddress: '', emailBusy: false,
emailBusy: false, };
};
},
onEmailAddressChanged: function(value) { onEmailAddressChanged = value => {
this.setState({ this.setState({
emailAddress: value, emailAddress: value,
}); });
}, };
onSubmit: function() { onSubmit = () => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
@ -81,21 +77,21 @@ export default createReactClass({
}); });
}); });
this.setState({emailBusy: true}); this.setState({emailBusy: true});
}, };
onCancelled: function() { onCancelled = () => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
onEmailDialogFinished: function(ok) { onEmailDialogFinished = ok => {
if (ok) { if (ok) {
this.verifyEmailAddress(); this.verifyEmailAddress();
} else { } else {
this.setState({emailBusy: false}); this.setState({emailBusy: false});
} }
}, };
verifyEmailAddress: function() { verifyEmailAddress() {
this._addThreepid.checkEmailLinkClicked().then(() => { this._addThreepid.checkEmailLinkClicked().then(() => {
this.props.onFinished(true); this.props.onFinished(true);
}, (err) => { }, (err) => {
@ -119,9 +115,9 @@ export default createReactClass({
}); });
} }
}); });
}, }
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const Spinner = sdk.getComponent('elements.Spinner'); const Spinner = sdk.getComponent('elements.Spinner');
const EditableText = sdk.getComponent('elements.EditableText'); const EditableText = sdk.getComponent('elements.EditableText');
@ -161,5 +157,5 @@ export default createReactClass({
</div> </div>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
@ -34,18 +33,22 @@ const USERNAME_CHECK_DEBOUNCE_MS = 250;
* *
* On success, `onFinished(true, newDisplayName)` is called. * On success, `onFinished(true, newDisplayName)` is called.
*/ */
export default createReactClass({ export default class SetMxIdDialog extends React.Component {
displayName: 'SetMxIdDialog', static propTypes = {
propTypes: {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
// Called when the user requests to register with a different homeserver // Called when the user requests to register with a different homeserver
onDifferentServerClicked: PropTypes.func.isRequired, onDifferentServerClicked: PropTypes.func.isRequired,
// Called if the user wants to switch to login instead // Called if the user wants to switch to login instead
onLoginClick: PropTypes.func.isRequired, onLoginClick: PropTypes.func.isRequired,
}, };
getInitialState: function() { constructor(props) {
return { super(props);
this._input_value = createRef();
this._uiAuth = createRef();
this.state = {
// The entered username // The entered username
username: '', username: '',
// Indicate ongoing work on the username // Indicate ongoing work on the username
@ -60,21 +63,15 @@ export default createReactClass({
// Indicate error with auth // Indicate error with auth
authError: '', authError: '',
}; };
}, }
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs componentDidMount() {
UNSAFE_componentWillMount: function() {
this._input_value = createRef();
this._uiAuth = createRef();
},
componentDidMount: function() {
this._input_value.current.select(); this._input_value.current.select();
this._matrixClient = MatrixClientPeg.get(); this._matrixClient = MatrixClientPeg.get();
}, }
onValueChange: function(ev) { onValueChange = ev => {
this.setState({ this.setState({
username: ev.target.value, username: ev.target.value,
usernameBusy: true, usernameBusy: true,
@ -99,24 +96,24 @@ export default createReactClass({
}); });
}, USERNAME_CHECK_DEBOUNCE_MS); }, USERNAME_CHECK_DEBOUNCE_MS);
}); });
}, };
onKeyUp: function(ev) { onKeyUp = ev => {
if (ev.key === Key.ENTER) { if (ev.key === Key.ENTER) {
this.onSubmit(); this.onSubmit();
} }
}, };
onSubmit: function(ev) { onSubmit = ev => {
if (this._uiAuth.current) { if (this._uiAuth.current) {
this._uiAuth.current.tryContinue(); this._uiAuth.current.tryContinue();
} }
this.setState({ this.setState({
doingUIAuth: true, doingUIAuth: true,
}); });
}, };
_doUsernameCheck: function() { _doUsernameCheck() {
// We do a quick check ahead of the username availability API to ensure the // We do a quick check ahead of the username availability API to ensure the
// user ID roughly looks okay from a Matrix perspective. // user ID roughly looks okay from a Matrix perspective.
if (!SAFE_LOCALPART_REGEX.test(this.state.username)) { if (!SAFE_LOCALPART_REGEX.test(this.state.username)) {
@ -167,13 +164,13 @@ export default createReactClass({
this.setState(newState); this.setState(newState);
}, },
); );
}, }
_generatePassword: function() { _generatePassword() {
return Math.random().toString(36).slice(2); return Math.random().toString(36).slice(2);
}, }
_makeRegisterRequest: function(auth) { _makeRegisterRequest = auth => {
// Not upgrading - changing mxids // Not upgrading - changing mxids
const guestAccessToken = null; const guestAccessToken = null;
if (!this._generatedPassword) { if (!this._generatedPassword) {
@ -187,9 +184,9 @@ export default createReactClass({
{}, {},
guestAccessToken, guestAccessToken,
); );
}, };
_onUIAuthFinished: function(success, response) { _onUIAuthFinished = (success, response) => {
this.setState({ this.setState({
doingUIAuth: false, doingUIAuth: false,
}); });
@ -207,9 +204,9 @@ export default createReactClass({
accessToken: response.access_token, accessToken: response.access_token,
password: this._generatedPassword, password: this._generatedPassword,
}); });
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth'); const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
@ -303,5 +300,5 @@ export default createReactClass({
</div> </div>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -63,32 +62,25 @@ const WarmFuzzy = function(props) {
* *
* On success, `onFinished()` when finished * On success, `onFinished()` when finished
*/ */
export default createReactClass({ export default class SetPasswordDialog extends React.Component {
displayName: 'SetPasswordDialog', static propTypes = {
propTypes: {
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}, };
getInitialState: function() { state = {
return { error: null,
error: null, };
};
},
componentDidMount: function() { _onPasswordChanged = res => {
console.info('SetPasswordDialog component did mount');
},
_onPasswordChanged: function(res) {
Modal.createDialog(WarmFuzzy, { Modal.createDialog(WarmFuzzy, {
didSetEmail: res.didSetEmail, didSetEmail: res.didSetEmail,
onFinished: () => { onFinished: () => {
this.props.onFinished(); this.props.onFinished();
}, },
}); });
}, };
_onPasswordChangeError: function(err) { _onPasswordChangeError = err => {
let errMsg = err.error || ""; let errMsg = err.error || "";
if (err.httpStatus === 403) { if (err.httpStatus === 403) {
errMsg = _t('Failed to change password. Is your password correct?'); errMsg = _t('Failed to change password. Is your password correct?');
@ -101,9 +93,9 @@ export default createReactClass({
this.setState({ this.setState({
error: errMsg, error: errMsg,
}); });
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const ChangePassword = sdk.getComponent('views.settings.ChangePassword'); const ChangePassword = sdk.getComponent('views.settings.ChangePassword');
@ -132,5 +124,5 @@ export default createReactClass({
</div> </div>
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -15,14 +15,12 @@ limitations under the License.
*/ */
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import Field from "../elements/Field"; import Field from "../elements/Field";
export default createReactClass({ export default class TextInputDialog extends React.Component {
displayName: 'TextInputDialog', static propTypes = {
propTypes: {
title: PropTypes.string, title: PropTypes.string,
description: PropTypes.oneOfType([ description: PropTypes.oneOfType([
PropTypes.element, PropTypes.element,
@ -36,39 +34,36 @@ export default createReactClass({
hasCancel: PropTypes.bool, hasCancel: PropTypes.bool,
validator: PropTypes.func, // result of withValidation validator: PropTypes.func, // result of withValidation
fixedWidth: PropTypes.bool, fixedWidth: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { title: "",
title: "", value: "",
value: "", description: "",
description: "", focus: true,
focus: true, hasCancel: true,
hasCancel: true, };
};
},
getInitialState: function() { constructor(props) {
return { super(props);
this._field = createRef();
this.state = {
value: this.props.value, value: this.props.value,
valid: false, valid: false,
}; };
}, }
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs componentDidMount() {
UNSAFE_componentWillMount: function() {
this._field = createRef();
},
componentDidMount: function() {
if (this.props.focus) { if (this.props.focus) {
// Set the cursor at the end of the text input // Set the cursor at the end of the text input
// this._field.current.value = this.props.value; // this._field.current.value = this.props.value;
this._field.current.focus(); this._field.current.focus();
} }
}, }
onOk: async function(ev) { onOk = async ev => {
ev.preventDefault(); ev.preventDefault();
if (this.props.validator) { if (this.props.validator) {
await this._field.current.validate({ allowEmpty: false }); await this._field.current.validate({ allowEmpty: false });
@ -80,27 +75,27 @@ export default createReactClass({
} }
} }
this.props.onFinished(true, this.state.value); this.props.onFinished(true, this.state.value);
}, };
onCancel: function() { onCancel = () => {
this.props.onFinished(false); this.props.onFinished(false);
}, };
onChange: function(ev) { onChange = ev => {
this.setState({ this.setState({
value: ev.target.value, value: ev.target.value,
}); });
}, };
onValidate: async function(fieldState) { onValidate = async fieldState => {
const result = await this.props.validator(fieldState); const result = await this.props.validator(fieldState);
this.setState({ this.setState({
valid: result.valid, valid: result.valid,
}); });
return result; return result;
}, };
render: function() { render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return ( return (
@ -137,5 +132,5 @@ export default createReactClass({
/> />
</BaseDialog> </BaseDialog>
); );
}, }
}); }

View file

@ -16,16 +16,13 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import AccessibleButton from './AccessibleButton'; import AccessibleButton from './AccessibleButton';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import Analytics from '../../../Analytics'; import Analytics from '../../../Analytics';
export default createReactClass({ export default class ActionButton extends React.Component {
displayName: 'RoleButton', static propTypes = {
propTypes: {
size: PropTypes.string, size: PropTypes.string,
tooltip: PropTypes.bool, tooltip: PropTypes.bool,
action: PropTypes.string.isRequired, action: PropTypes.string.isRequired,
@ -33,39 +30,35 @@ export default createReactClass({
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
iconPath: PropTypes.string, iconPath: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
}, };
getDefaultProps: function() { static defaultProps = {
return { size: "25",
size: "25", tooltip: false,
tooltip: false, };
};
},
getInitialState: function() { state = {
return { showTooltip: false,
showTooltip: false, };
};
},
_onClick: function(ev) { _onClick = (ev) => {
ev.stopPropagation(); ev.stopPropagation();
Analytics.trackEvent('Action Button', 'click', this.props.action); Analytics.trackEvent('Action Button', 'click', this.props.action);
dis.dispatch({action: this.props.action}); dis.dispatch({action: this.props.action});
}, };
_onMouseEnter: function() { _onMouseEnter = () => {
if (this.props.tooltip) this.setState({showTooltip: true}); if (this.props.tooltip) this.setState({showTooltip: true});
if (this.props.mouseOverAction) { if (this.props.mouseOverAction) {
dis.dispatch({action: this.props.mouseOverAction}); dis.dispatch({action: this.props.mouseOverAction});
} }
}, };
_onMouseLeave: function() { _onMouseLeave = () => {
this.setState({showTooltip: false}); this.setState({showTooltip: false});
}, };
render: function() { render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
let tooltip; let tooltip;
@ -94,5 +87,5 @@ export default createReactClass({
{ tooltip } { tooltip }
</AccessibleButton> </AccessibleButton>
); );
}, }
}); }

View file

@ -17,15 +17,12 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import classNames from 'classnames'; import classNames from 'classnames';
import { UserAddressType } from '../../../UserAddress'; import { UserAddressType } from '../../../UserAddress';
export default createReactClass({ export default class AddressSelector extends React.Component {
displayName: 'AddressSelector', static propTypes = {
propTypes: {
onSelected: PropTypes.func.isRequired, onSelected: PropTypes.func.isRequired,
// List of the addresses to display // List of the addresses to display
@ -37,90 +34,91 @@ export default createReactClass({
// Element to put as a header on top of the list // Element to put as a header on top of the list
header: PropTypes.node, header: PropTypes.node,
}, };
getInitialState: function() { constructor(props) {
return { super(props);
this.state = {
selected: this.props.selected === undefined ? 0 : this.props.selected, selected: this.props.selected === undefined ? 0 : this.props.selected,
hover: false, hover: false,
}; };
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(props) { UNSAFE_componentWillReceiveProps(props) {
// Make sure the selected item isn't outside the list bounds // Make sure the selected item isn't outside the list bounds
const selected = this.state.selected; const selected = this.state.selected;
const maxSelected = this._maxSelected(props.addressList); const maxSelected = this._maxSelected(props.addressList);
if (selected > maxSelected) { if (selected > maxSelected) {
this.setState({ selected: maxSelected }); this.setState({ selected: maxSelected });
} }
}, }
componentDidUpdate: function() { componentDidUpdate() {
// As the user scrolls with the arrow keys keep the selected item // As the user scrolls with the arrow keys keep the selected item
// at the top of the window. // at the top of the window.
if (this.scrollElement && this.props.addressList.length > 0 && !this.state.hover) { if (this.scrollElement && this.props.addressList.length > 0 && !this.state.hover) {
const elementHeight = this.addressListElement.getBoundingClientRect().height; const elementHeight = this.addressListElement.getBoundingClientRect().height;
this.scrollElement.scrollTop = (this.state.selected * elementHeight) - elementHeight; this.scrollElement.scrollTop = (this.state.selected * elementHeight) - elementHeight;
} }
}, }
moveSelectionTop: function() { moveSelectionTop = () => {
if (this.state.selected > 0) { if (this.state.selected > 0) {
this.setState({ this.setState({
selected: 0, selected: 0,
hover: false, hover: false,
}); });
} }
}, };
moveSelectionUp: function() { moveSelectionUp = () => {
if (this.state.selected > 0) { if (this.state.selected > 0) {
this.setState({ this.setState({
selected: this.state.selected - 1, selected: this.state.selected - 1,
hover: false, hover: false,
}); });
} }
}, };
moveSelectionDown: function() { moveSelectionDown = () => {
if (this.state.selected < this._maxSelected(this.props.addressList)) { if (this.state.selected < this._maxSelected(this.props.addressList)) {
this.setState({ this.setState({
selected: this.state.selected + 1, selected: this.state.selected + 1,
hover: false, hover: false,
}); });
} }
}, };
chooseSelection: function() { chooseSelection = () => {
this.selectAddress(this.state.selected); this.selectAddress(this.state.selected);
}, };
onClick: function(index) { onClick = index => {
this.selectAddress(index); this.selectAddress(index);
}, };
onMouseEnter: function(index) { onMouseEnter = index => {
this.setState({ this.setState({
selected: index, selected: index,
hover: true, hover: true,
}); });
}, };
onMouseLeave: function() { onMouseLeave = () => {
this.setState({ hover: false }); this.setState({ hover: false });
}, };
selectAddress: function(index) { selectAddress = index => {
// Only try to select an address if one exists // Only try to select an address if one exists
if (this.props.addressList.length !== 0) { if (this.props.addressList.length !== 0) {
this.props.onSelected(index); this.props.onSelected(index);
this.setState({ hover: false }); this.setState({ hover: false });
} }
}, };
createAddressListTiles: function() { createAddressListTiles() {
const self = this;
const AddressTile = sdk.getComponent("elements.AddressTile"); const AddressTile = sdk.getComponent("elements.AddressTile");
const maxSelected = this._maxSelected(this.props.addressList); const maxSelected = this._maxSelected(this.props.addressList);
const addressList = []; const addressList = [];
@ -157,15 +155,15 @@ export default createReactClass({
} }
} }
return addressList; return addressList;
}, }
_maxSelected: function(list) { _maxSelected(list) {
const listSize = list.length === 0 ? 0 : list.length - 1; const listSize = list.length === 0 ? 0 : list.length - 1;
const maxSelected = listSize > (this.props.truncateAt - 1) ? (this.props.truncateAt - 1) : listSize; const maxSelected = listSize > (this.props.truncateAt - 1) ? (this.props.truncateAt - 1) : listSize;
return maxSelected; return maxSelected;
}, }
render: function() { render() {
const classes = classNames({ const classes = classNames({
"mx_AddressSelector": true, "mx_AddressSelector": true,
"mx_AddressSelector_empty": this.props.addressList.length === 0, "mx_AddressSelector_empty": this.props.addressList.length === 0,
@ -177,5 +175,5 @@ export default createReactClass({
{ this.createAddressListTiles() } { this.createAddressListTiles() }
</div> </div>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames'; import classNames from 'classnames';
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
@ -25,25 +24,21 @@ import { _t } from '../../../languageHandler';
import { UserAddressType } from '../../../UserAddress.js'; import { UserAddressType } from '../../../UserAddress.js';
export default createReactClass({ export default class AddressTile extends React.Component {
displayName: 'AddressTile', static propTypes = {
propTypes: {
address: UserAddressType.isRequired, address: UserAddressType.isRequired,
canDismiss: PropTypes.bool, canDismiss: PropTypes.bool,
onDismissed: PropTypes.func, onDismissed: PropTypes.func,
justified: PropTypes.bool, justified: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { canDismiss: false,
canDismiss: false, onDismissed: function() {}, // NOP
onDismissed: function() {}, // NOP justified: false,
justified: false, };
};
},
render: function() { render() {
const address = this.props.address; const address = this.props.address;
const name = address.displayName || address.address; const name = address.displayName || address.address;
@ -144,5 +139,5 @@ export default createReactClass({
{ dismiss } { dismiss }
</div> </div>
); );
}, }
}); }

View file

@ -18,16 +18,13 @@ limitations under the License.
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
/** /**
* Basic container for buttons in modal dialogs. * Basic container for buttons in modal dialogs.
*/ */
export default createReactClass({ export default class DialogButtons extends React.Component {
displayName: "DialogButtons", static propTypes = {
propTypes: {
// The primary button which is styled differently and has default focus. // The primary button which is styled differently and has default focus.
primaryButton: PropTypes.node.isRequired, primaryButton: PropTypes.node.isRequired,
@ -57,20 +54,18 @@ export default createReactClass({
// disables only the primary button // disables only the primary button
primaryDisabled: PropTypes.bool, primaryDisabled: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { hasCancel: true,
hasCancel: true, disabled: false,
disabled: false, };
};
},
_onCancelClick: function() { _onCancelClick = () => {
this.props.onCancel(); this.props.onCancel();
}, };
render: function() { render() {
let primaryButtonClassName = "mx_Dialog_primary"; let primaryButtonClassName = "mx_Dialog_primary";
if (this.props.primaryButtonClass) { if (this.props.primaryButtonClass) {
primaryButtonClassName += " " + this.props.primaryButtonClass; primaryButtonClassName += " " + this.props.primaryButtonClass;
@ -104,5 +99,5 @@ export default createReactClass({
</button> </button>
</div> </div>
); );
}, }
}); }

View file

@ -17,13 +17,10 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {Key} from "../../../Keyboard"; import {Key} from "../../../Keyboard";
export default createReactClass({ export default class EditableText extends React.Component {
displayName: 'EditableText', static propTypes = {
propTypes: {
onValueChanged: PropTypes.func, onValueChanged: PropTypes.func,
initialValue: PropTypes.string, initialValue: PropTypes.string,
label: PropTypes.string, label: PropTypes.string,
@ -36,60 +33,62 @@ export default createReactClass({
// Will cause onValueChanged(value, true) to fire on blur // Will cause onValueChanged(value, true) to fire on blur
blurToSubmit: PropTypes.bool, blurToSubmit: PropTypes.bool,
editable: PropTypes.bool, editable: PropTypes.bool,
}, };
Phases: { static Phases = {
Display: "display", Display: "display",
Edit: "edit", Edit: "edit",
}, };
getDefaultProps: function() { static defaultProps = {
return { onValueChanged() {},
onValueChanged: function() {}, initialValue: '',
initialValue: '', label: '',
label: '', placeholder: '',
placeholder: '', editable: true,
editable: true, className: "mx_EditableText",
className: "mx_EditableText", placeholderClassName: "mx_EditableText_placeholder",
placeholderClassName: "mx_EditableText_placeholder", blurToSubmit: false,
blurToSubmit: false, };
};
},
getInitialState: function() { constructor(props) {
return { super(props);
phase: this.Phases.Display,
};
},
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(nextProps) {
if (nextProps.initialValue !== this.props.initialValue) {
this.value = nextProps.initialValue;
if (this._editable_div.current) {
this.showPlaceholder(!this.value);
}
}
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
// we track value as an JS object field rather than in React state // we track value as an JS object field rather than in React state
// as React doesn't play nice with contentEditable. // as React doesn't play nice with contentEditable.
this.value = ''; this.value = '';
this.placeholder = false; this.placeholder = false;
this._editable_div = createRef(); this._editable_div = createRef();
},
componentDidMount: function() { this.state = {
phase: EditableText.Phases.Display,
};
}
state = {
phase: EditableText.Phases.Display,
};
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.initialValue !== this.props.initialValue) {
this.value = nextProps.initialValue;
if (this._editable_div.current) {
this.showPlaceholder(!this.value);
}
}
}
componentDidMount() {
this.value = this.props.initialValue; this.value = this.props.initialValue;
if (this._editable_div.current) { if (this._editable_div.current) {
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);
} }
}, }
showPlaceholder: function(show) { showPlaceholder = show => {
if (show) { if (show) {
this._editable_div.current.textContent = this.props.placeholder; this._editable_div.current.textContent = this.props.placeholder;
this._editable_div.current.setAttribute("class", this.props.className this._editable_div.current.setAttribute("class", this.props.className
@ -101,38 +100,36 @@ export default createReactClass({
this._editable_div.current.setAttribute("class", this.props.className); this._editable_div.current.setAttribute("class", this.props.className);
this.placeholder = false; this.placeholder = false;
} }
}, };
getValue: function() { getValue = () => this.value;
return this.value;
},
setValue: function(value) { setValue = value => {
this.value = value; this.value = value;
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);
}, };
edit: function() { edit = () => {
this.setState({ this.setState({
phase: this.Phases.Edit, phase: EditableText.Phases.Edit,
}); });
}, };
cancelEdit: function() { cancelEdit = () => {
this.setState({ this.setState({
phase: this.Phases.Display, phase: EditableText.Phases.Display,
}); });
this.value = this.props.initialValue; this.value = this.props.initialValue;
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);
this.onValueChanged(false); this.onValueChanged(false);
this._editable_div.current.blur(); this._editable_div.current.blur();
}, };
onValueChanged: function(shouldSubmit) { onValueChanged = shouldSubmit => {
this.props.onValueChanged(this.value, shouldSubmit); this.props.onValueChanged(this.value, shouldSubmit);
}, };
onKeyDown: function(ev) { onKeyDown = ev => {
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); // console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
if (this.placeholder) { if (this.placeholder) {
@ -145,9 +142,9 @@ export default createReactClass({
} }
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); // console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
}, };
onKeyUp: function(ev) { onKeyUp = ev => {
// console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); // console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
if (!ev.target.textContent) { if (!ev.target.textContent) {
@ -163,17 +160,17 @@ export default createReactClass({
} }
// console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); // console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
}, };
onClickDiv: function(ev) { onClickDiv = ev => {
if (!this.props.editable) return; if (!this.props.editable) return;
this.setState({ this.setState({
phase: this.Phases.Edit, phase: EditableText.Phases.Edit,
}); });
}, };
onFocus: function(ev) { onFocus = ev => {
//ev.target.setSelectionRange(0, ev.target.textContent.length); //ev.target.setSelectionRange(0, ev.target.textContent.length);
const node = ev.target.childNodes[0]; const node = ev.target.childNodes[0];
@ -186,21 +183,21 @@ export default createReactClass({
sel.removeAllRanges(); sel.removeAllRanges();
sel.addRange(range); sel.addRange(range);
} }
}, };
onFinish: function(ev, shouldSubmit) { onFinish = (ev, shouldSubmit) => {
const self = this; const self = this;
const submit = (ev.key === Key.ENTER) || shouldSubmit; const submit = (ev.key === Key.ENTER) || shouldSubmit;
this.setState({ this.setState({
phase: this.Phases.Display, phase: EditableText.Phases.Display,
}, () => { }, () => {
if (this.value !== this.props.initialValue) { if (this.value !== this.props.initialValue) {
self.onValueChanged(submit); self.onValueChanged(submit);
} }
}); });
}, };
onBlur: function(ev) { onBlur = ev => {
const sel = window.getSelection(); const sel = window.getSelection();
sel.removeAllRanges(); sel.removeAllRanges();
@ -211,13 +208,13 @@ export default createReactClass({
} }
this.showPlaceholder(!this.value); this.showPlaceholder(!this.value);
}, };
render: function() { render() {
const {className, editable, initialValue, label, labelClassName} = this.props; const {className, editable, initialValue, label, labelClassName} = this.props;
let editableEl; let editableEl;
if (!editable || (this.state.phase === this.Phases.Display && (label || labelClassName) && !this.value)) { if (!editable || (this.state.phase === EditableText.Phases.Display && (label || labelClassName) && !this.value)) {
// show the label // show the label
editableEl = <div className={className + " " + labelClassName} onClick={this.onClickDiv}> editableEl = <div className={className + " " + labelClassName} onClick={this.onClickDiv}>
{ label || initialValue } { label || initialValue }
@ -234,5 +231,5 @@ export default createReactClass({
} }
return editableEl; return editableEl;
}, }
}); }

View file

@ -15,14 +15,11 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import createReactClass from 'create-react-class';
import {_t} from "../../../languageHandler"; import {_t} from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
export default createReactClass({ export default class InlineSpinner extends React.Component {
displayName: 'InlineSpinner', render() {
render: function() {
const w = this.props.w || 16; const w = this.props.w || 16;
const h = this.props.h || 16; const h = this.props.h || 16;
const imgClass = this.props.imgClassName || ""; const imgClass = this.props.imgClassName || "";
@ -45,5 +42,5 @@ export default createReactClass({
/> />
</div> </div>
); );
}, }
}); }

View file

@ -18,17 +18,14 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils'; import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import {MatrixEvent} from "matrix-js-sdk"; import {MatrixEvent} from "matrix-js-sdk";
import {isValid3pidInvite} from "../../../RoomInvite"; import {isValid3pidInvite} from "../../../RoomInvite";
export default createReactClass({ export default class MemberEventListSummary extends React.Component {
displayName: 'MemberEventListSummary', static propTypes = {
propTypes: {
// An array of member events to summarise // An array of member events to summarise
events: PropTypes.arrayOf(PropTypes.instanceOf(MatrixEvent)).isRequired, events: PropTypes.arrayOf(PropTypes.instanceOf(MatrixEvent)).isRequired,
// An array of EventTiles to render when expanded // An array of EventTiles to render when expanded
@ -43,17 +40,15 @@ export default createReactClass({
onToggle: PropTypes.func, onToggle: PropTypes.func,
// Whether or not to begin with state.expanded=true // Whether or not to begin with state.expanded=true
startExpanded: PropTypes.bool, startExpanded: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { summaryLength: 1,
summaryLength: 1, threshold: 3,
threshold: 3, avatarsMaxLength: 5,
avatarsMaxLength: 5, };
};
},
shouldComponentUpdate: function(nextProps) { shouldComponentUpdate(nextProps) {
// Update if // Update if
// - The number of summarised events has changed // - The number of summarised events has changed
// - or if the summary is about to toggle to become collapsed // - or if the summary is about to toggle to become collapsed
@ -62,7 +57,7 @@ export default createReactClass({
nextProps.events.length !== this.props.events.length || nextProps.events.length !== this.props.events.length ||
nextProps.events.length < this.props.threshold nextProps.events.length < this.props.threshold
); );
}, }
/** /**
* Generate the text for users aggregated by their transition sequences (`eventAggregates`) where * Generate the text for users aggregated by their transition sequences (`eventAggregates`) where
@ -73,7 +68,7 @@ export default createReactClass({
* `Object.keys(eventAggregates)`. * `Object.keys(eventAggregates)`.
* @returns {string} the textual summary of the aggregated events that occurred. * @returns {string} the textual summary of the aggregated events that occurred.
*/ */
_generateSummary: function(eventAggregates, orderedTransitionSequences) { _generateSummary(eventAggregates, orderedTransitionSequences) {
const summaries = orderedTransitionSequences.map((transitions) => { const summaries = orderedTransitionSequences.map((transitions) => {
const userNames = eventAggregates[transitions]; const userNames = eventAggregates[transitions];
const nameList = this._renderNameList(userNames); const nameList = this._renderNameList(userNames);
@ -105,7 +100,7 @@ export default createReactClass({
} }
return summaries.join(", "); return summaries.join(", ");
}, }
/** /**
* @param {string[]} users an array of user display names or user IDs. * @param {string[]} users an array of user display names or user IDs.
@ -113,9 +108,9 @@ export default createReactClass({
* more items in `users` than `this.props.summaryLength`, which is the number of names * more items in `users` than `this.props.summaryLength`, which is the number of names
* included before "and [n] others". * included before "and [n] others".
*/ */
_renderNameList: function(users) { _renderNameList(users) {
return formatCommaSeparatedList(users, this.props.summaryLength); return formatCommaSeparatedList(users, this.props.summaryLength);
}, }
/** /**
* Canonicalise an array of transitions such that some pairs of transitions become * Canonicalise an array of transitions such that some pairs of transitions become
@ -124,7 +119,7 @@ export default createReactClass({
* @param {string[]} transitions an array of transitions. * @param {string[]} transitions an array of transitions.
* @returns {string[]} an array of transitions. * @returns {string[]} an array of transitions.
*/ */
_getCanonicalTransitions: function(transitions) { _getCanonicalTransitions(transitions) {
const modMap = { const modMap = {
'joined': { 'joined': {
'after': 'left', 'after': 'left',
@ -155,7 +150,7 @@ export default createReactClass({
res.push(transition); res.push(transition);
} }
return res; return res;
}, }
/** /**
* Transform an array of transitions into an array of transitions and how many times * Transform an array of transitions into an array of transitions and how many times
@ -171,7 +166,7 @@ export default createReactClass({
* @param {string[]} transitions the array of transitions to transform. * @param {string[]} transitions the array of transitions to transform.
* @returns {object[]} an array of coalesced transitions. * @returns {object[]} an array of coalesced transitions.
*/ */
_coalesceRepeatedTransitions: function(transitions) { _coalesceRepeatedTransitions(transitions) {
const res = []; const res = [];
for (let i = 0; i < transitions.length; i++) { for (let i = 0; i < transitions.length; i++) {
if (res.length > 0 && res[res.length - 1].transitionType === transitions[i]) { if (res.length > 0 && res[res.length - 1].transitionType === transitions[i]) {
@ -184,7 +179,7 @@ export default createReactClass({
} }
} }
return res; return res;
}, }
/** /**
* For a certain transition, t, describe what happened to the users that * For a certain transition, t, describe what happened to the users that
@ -268,11 +263,11 @@ export default createReactClass({
} }
return res; return res;
}, }
_getTransitionSequence: function(events) { _getTransitionSequence(events) {
return events.map(this._getTransition); return events.map(this._getTransition);
}, }
/** /**
* Label a given membership event, `e`, where `getContent().membership` has * Label a given membership event, `e`, where `getContent().membership` has
@ -282,7 +277,7 @@ export default createReactClass({
* @returns {string?} the transition type given to this event. This defaults to `null` * @returns {string?} the transition type given to this event. This defaults to `null`
* if a transition is not recognised. * if a transition is not recognised.
*/ */
_getTransition: function(e) { _getTransition(e) {
if (e.mxEvent.getType() === 'm.room.third_party_invite') { if (e.mxEvent.getType() === 'm.room.third_party_invite') {
// Handle 3pid invites the same as invites so they get bundled together // Handle 3pid invites the same as invites so they get bundled together
if (!isValid3pidInvite(e.mxEvent)) { if (!isValid3pidInvite(e.mxEvent)) {
@ -323,9 +318,9 @@ export default createReactClass({
} }
default: return null; default: return null;
} }
}, }
_getAggregate: function(userEvents) { _getAggregate(userEvents) {
// A map of aggregate type to arrays of display names. Each aggregate type // A map of aggregate type to arrays of display names. Each aggregate type
// is a comma-delimited string of transitions, e.g. "joined,left,kicked". // is a comma-delimited string of transitions, e.g. "joined,left,kicked".
// The array of display names is the array of users who went through that // The array of display names is the array of users who went through that
@ -364,9 +359,9 @@ export default createReactClass({
names: aggregate, names: aggregate,
indices: aggregateIndices, indices: aggregateIndices,
}; };
}, }
render: function() { render() {
const eventsToRender = this.props.events; const eventsToRender = this.props.events;
// Map user IDs to an array of objects: // Map user IDs to an array of objects:
@ -420,5 +415,5 @@ export default createReactClass({
children={this.props.children} children={this.props.children}
summaryMembers={avatarMembers} summaryMembers={avatarMembers}
summaryText={this._generateSummary(aggregate.names, orderedTransitionSequences)} />; summaryText={this._generateSummary(aggregate.names, orderedTransitionSequences)} />;
}, }
}); }

View file

@ -16,49 +16,44 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import RoomViewStore from '../../../stores/RoomViewStore'; import RoomViewStore from '../../../stores/RoomViewStore';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import WidgetUtils from '../../../utils/WidgetUtils'; import WidgetUtils from '../../../utils/WidgetUtils';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
export default createReactClass({ export default class PersistentApp extends React.Component {
displayName: 'PersistentApp', state = {
roomId: RoomViewStore.getRoomId(),
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
};
getInitialState: function() { componentDidMount() {
return {
roomId: RoomViewStore.getRoomId(),
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
};
},
componentDidMount: function() {
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate); ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate);
}, }
componentWillUnmount: function() { componentWillUnmount() {
if (this._roomStoreToken) { if (this._roomStoreToken) {
this._roomStoreToken.remove(); this._roomStoreToken.remove();
} }
ActiveWidgetStore.removeListener('update', this._onActiveWidgetStoreUpdate); ActiveWidgetStore.removeListener('update', this._onActiveWidgetStoreUpdate);
}, }
_onRoomViewStoreUpdate: function(payload) { _onRoomViewStoreUpdate = payload => {
if (RoomViewStore.getRoomId() === this.state.roomId) return; if (RoomViewStore.getRoomId() === this.state.roomId) return;
this.setState({ this.setState({
roomId: RoomViewStore.getRoomId(), roomId: RoomViewStore.getRoomId(),
}); });
}, };
_onActiveWidgetStoreUpdate: function() { _onActiveWidgetStoreUpdate = () => {
this.setState({ this.setState({
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(), persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
}); });
}, };
render: function() { render() {
if (this.state.persistentWidgetId) { if (this.state.persistentWidgetId) {
const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId); const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId);
if (this.state.roomId !== persistentWidgetInRoomId) { if (this.state.roomId !== persistentWidgetInRoomId) {
@ -91,6 +86,6 @@ export default createReactClass({
} }
} }
return null; return null;
}, }
}); }

View file

@ -16,7 +16,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import classNames from 'classnames'; import classNames from 'classnames';
@ -32,27 +31,29 @@ import {Action} from "../../../dispatcher/actions";
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`) // HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/; const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
const Pill = createReactClass({ class Pill extends React.Component {
statics: { static isPillUrl(url) {
isPillUrl: (url) => { return !!getPrimaryPermalinkEntity(url);
return !!getPrimaryPermalinkEntity(url); }
},
isMessagePillUrl: (url) => {
return !!REGEX_LOCAL_PERMALINK.exec(url);
},
roomNotifPos: (text) => {
return text.indexOf("@room");
},
roomNotifLen: () => {
return "@room".length;
},
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
TYPE_GROUP_MENTION: 'TYPE_GROUP_MENTION',
TYPE_AT_ROOM_MENTION: 'TYPE_AT_ROOM_MENTION', // '@room' mention
},
props: { static isMessagePillUrl(url) {
return !!REGEX_LOCAL_PERMALINK.exec(url);
}
static roomNotifPos(text) {
return text.indexOf("@room");
}
static roomNotifLen() {
return "@room".length;
}
static TYPE_USER_MENTION = 'TYPE_USER_MENTION';
static TYPE_ROOM_MENTION = 'TYPE_ROOM_MENTION';
static TYPE_GROUP_MENTION = 'TYPE_GROUP_MENTION';
static TYPE_AT_ROOM_MENTION = 'TYPE_AT_ROOM_MENTION'; // '@room' mention
static propTypes = {
// The Type of this Pill. If url is given, this is auto-detected. // The Type of this Pill. If url is given, this is auto-detected.
type: PropTypes.string, type: PropTypes.string,
// The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl) // The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl)
@ -65,23 +66,21 @@ const Pill = createReactClass({
shouldShowPillAvatar: PropTypes.bool, shouldShowPillAvatar: PropTypes.bool,
// Whether to render this pill as if it were highlit by a selection // Whether to render this pill as if it were highlit by a selection
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
}, };
getInitialState() { state = {
return { // ID/alias of the room/user
// ID/alias of the room/user resourceId: null,
resourceId: null, // Type of pill
// Type of pill pillType: null,
pillType: null,
// The member related to the user pill // The member related to the user pill
member: null, member: null,
// The group related to the group pill // The group related to the group pill
group: null, group: null,
// The room related to the room pill // The room related to the room pill
room: null, room: null,
}; };
},
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
async UNSAFE_componentWillReceiveProps(nextProps) { async UNSAFE_componentWillReceiveProps(nextProps) {
@ -155,7 +154,7 @@ const Pill = createReactClass({
} }
} }
this.setState({resourceId, pillType, member, group, room}); this.setState({resourceId, pillType, member, group, room});
}, }
componentDidMount() { componentDidMount() {
this._unmounted = false; this._unmounted = false;
@ -163,13 +162,13 @@ const Pill = createReactClass({
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
this.UNSAFE_componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves. this.UNSAFE_componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves.
}, }
componentWillUnmount() { componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
}, }
doProfileLookup: function(userId, member) { doProfileLookup(userId, member) {
MatrixClientPeg.get().getProfileInfo(userId).then((resp) => { MatrixClientPeg.get().getProfileInfo(userId).then((resp) => {
if (this._unmounted) { if (this._unmounted) {
return; return;
@ -188,15 +187,16 @@ const Pill = createReactClass({
}).catch((err) => { }).catch((err) => {
console.error('Could not retrieve profile data for ' + userId + ':', err); console.error('Could not retrieve profile data for ' + userId + ':', err);
}); });
}, }
onUserPillClicked: function() { onUserPillClicked = () => {
dis.dispatch({ dis.dispatch({
action: Action.ViewUser, action: Action.ViewUser,
member: this.state.member, member: this.state.member,
}); });
}, };
render: function() {
render() {
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
@ -285,7 +285,7 @@ const Pill = createReactClass({
// Deliberately render nothing if the URL isn't recognised // Deliberately render nothing if the URL isn't recognised
return null; return null;
} }
}, }
}); }
export default Pill; export default Pill;

View file

@ -16,16 +16,13 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as Roles from '../../../Roles'; import * as Roles from '../../../Roles';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import Field from "./Field"; import Field from "./Field";
import {Key} from "../../../Keyboard"; import {Key} from "../../../Keyboard";
export default createReactClass({ export default class PowerSelector extends React.Component {
displayName: 'PowerSelector', static propTypes = {
propTypes: {
value: PropTypes.number.isRequired, value: PropTypes.number.isRequired,
// The maximum value that can be set with the power selector // The maximum value that can be set with the power selector
maxValue: PropTypes.number.isRequired, maxValue: PropTypes.number.isRequired,
@ -42,10 +39,17 @@ export default createReactClass({
// The name to annotate the selector with // The name to annotate the selector with
label: PropTypes.string, label: PropTypes.string,
}, }
getInitialState: function() { static defaultProps = {
return { maxValue: Infinity,
usersDefault: 0,
};
constructor(props) {
super(props);
this.state = {
levelRoleMap: {}, levelRoleMap: {},
// List of power levels to show in the drop-down // List of power levels to show in the drop-down
options: [], options: [],
@ -53,26 +57,16 @@ export default createReactClass({
customValue: this.props.value, customValue: this.props.value,
selectValue: 0, selectValue: 0,
}; };
},
getDefaultProps: function() {
return {
maxValue: Infinity,
usersDefault: 0,
};
},
componentDidMount: function() {
// TODO: [REACT-WARNING] Move this to class constructor
this._initStateFromProps(this.props); this._initStateFromProps(this.props);
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(newProps) { UNSAFE_componentWillReceiveProps(newProps) {
this._initStateFromProps(newProps); this._initStateFromProps(newProps);
}, }
_initStateFromProps: function(newProps) { _initStateFromProps(newProps) {
// This needs to be done now because levelRoleMap has translated strings // This needs to be done now because levelRoleMap has translated strings
const levelRoleMap = Roles.levelRoleMap(newProps.usersDefault); const levelRoleMap = Roles.levelRoleMap(newProps.usersDefault);
const options = Object.keys(levelRoleMap).filter(level => { const options = Object.keys(levelRoleMap).filter(level => {
@ -92,9 +86,9 @@ export default createReactClass({
customLevel: newProps.value, customLevel: newProps.value,
selectValue: isCustom ? "SELECT_VALUE_CUSTOM" : newProps.value, selectValue: isCustom ? "SELECT_VALUE_CUSTOM" : newProps.value,
}); });
}, }
onSelectChange: function(event) { onSelectChange = event => {
const isCustom = event.target.value === "SELECT_VALUE_CUSTOM"; const isCustom = event.target.value === "SELECT_VALUE_CUSTOM";
if (isCustom) { if (isCustom) {
this.setState({custom: true}); this.setState({custom: true});
@ -102,20 +96,20 @@ export default createReactClass({
this.props.onChange(event.target.value, this.props.powerLevelKey); this.props.onChange(event.target.value, this.props.powerLevelKey);
this.setState({selectValue: event.target.value}); this.setState({selectValue: event.target.value});
} }
}, };
onCustomChange: function(event) { onCustomChange = event => {
this.setState({customValue: event.target.value}); this.setState({customValue: event.target.value});
}, };
onCustomBlur: function(event) { onCustomBlur = event => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
this.props.onChange(parseInt(this.state.customValue), this.props.powerLevelKey); this.props.onChange(parseInt(this.state.customValue), this.props.powerLevelKey);
}, };
onCustomKeyDown: function(event) { onCustomKeyDown = event => {
if (event.key === Key.ENTER) { if (event.key === Key.ENTER) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -127,9 +121,9 @@ export default createReactClass({
// handle the onBlur safely. // handle the onBlur safely.
event.target.blur(); event.target.blur();
} }
}, };
render: function() { render() {
let picker; let picker;
const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label; const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label;
if (this.state.custom) { if (this.state.custom) {
@ -166,5 +160,5 @@ export default createReactClass({
{ picker } { picker }
</div> </div>
); );
}, }
}); }

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames'; import classNames from 'classnames';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
@ -37,10 +36,8 @@ import SettingsStore from "../../../settings/SettingsStore";
// - Rooms that are part of the group // - Rooms that are part of the group
// - Direct messages with members of the group // - Direct messages with members of the group
// with the intention that this could be expanded to arbitrary tags in future. // with the intention that this could be expanded to arbitrary tags in future.
export default createReactClass({ export default class TagTile extends React.Component {
displayName: 'TagTile', static propTypes = {
propTypes: {
// A string tag such as "m.favourite" or a group ID such as "+groupid:domain.bla" // A string tag such as "m.favourite" or a group ID such as "+groupid:domain.bla"
// For now, only group IDs are handled. // For now, only group IDs are handled.
tag: PropTypes.string, tag: PropTypes.string,
@ -48,20 +45,16 @@ export default createReactClass({
openMenu: PropTypes.func, openMenu: PropTypes.func,
menuDisplayed: PropTypes.bool, menuDisplayed: PropTypes.bool,
selected: PropTypes.bool, selected: PropTypes.bool,
}, };
statics: { static contextType = MatrixClientContext;
contextType: MatrixClientContext,
},
getInitialState() { state = {
return { // Whether the mouse is over the tile
// Whether the mouse is over the tile hover: false,
hover: false, // The profile data of the group if this.props.tag is a group ID
// The profile data of the group if this.props.tag is a group ID profile: null,
profile: null, };
};
},
componentDidMount() { componentDidMount() {
this.unmounted = false; this.unmounted = false;
@ -71,16 +64,16 @@ export default createReactClass({
// New rooms or members may have been added to the group, fetch async // New rooms or members may have been added to the group, fetch async
this._refreshGroup(this.props.tag); this._refreshGroup(this.props.tag);
} }
}, }
componentWillUnmount() { componentWillUnmount() {
this.unmounted = true; this.unmounted = true;
if (this.props.tag[0] === '+') { if (this.props.tag[0] === '+') {
FlairStore.removeListener('updateGroupProfile', this._onFlairStoreUpdated); FlairStore.removeListener('updateGroupProfile', this._onFlairStoreUpdated);
} }
}, }
_onFlairStoreUpdated() { _onFlairStoreUpdated = () => {
if (this.unmounted) return; if (this.unmounted) return;
FlairStore.getGroupProfileCached( FlairStore.getGroupProfileCached(
this.context, this.context,
@ -91,14 +84,14 @@ export default createReactClass({
}).catch((err) => { }).catch((err) => {
console.warn('Could not fetch group profile for ' + this.props.tag, err); console.warn('Could not fetch group profile for ' + this.props.tag, err);
}); });
}, };
_refreshGroup(groupId) { _refreshGroup(groupId) {
GroupStore.refreshGroupRooms(groupId); GroupStore.refreshGroupRooms(groupId);
GroupStore.refreshGroupMembers(groupId); GroupStore.refreshGroupMembers(groupId);
}, }
onClick: function(e) { onClick = e => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
dis.dispatch({ dis.dispatch({
@ -111,27 +104,27 @@ export default createReactClass({
// New rooms or members may have been added to the group, fetch async // New rooms or members may have been added to the group, fetch async
this._refreshGroup(this.props.tag); this._refreshGroup(this.props.tag);
} }
}, };
onMouseOver: function() { onMouseOver = () => {
if (SettingsStore.getValue("feature_communities_v2_prototypes")) return; if (SettingsStore.getValue("feature_communities_v2_prototypes")) return;
this.setState({ hover: true }); this.setState({ hover: true });
}, };
onMouseLeave: function() { onMouseLeave = () => {
this.setState({ hover: false }); this.setState({ hover: false });
}, };
openMenu: function(e) { openMenu = e => {
// Prevent the TagTile onClick event firing as well // Prevent the TagTile onClick event firing as well
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (SettingsStore.getValue("feature_communities_v2_prototypes")) return; if (SettingsStore.getValue("feature_communities_v2_prototypes")) return;
this.setState({ hover: false }); this.setState({ hover: false });
this.props.openMenu(); this.props.openMenu();
}, };
render: function() { render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const profile = this.state.profile || {}; const profile = this.state.profile || {};
const name = profile.name || this.props.tag; const name = profile.name || this.props.tag;
@ -192,5 +185,5 @@ export default createReactClass({
{badgeElement} {badgeElement}
</div> </div>
</AccessibleTooltipButton>; </AccessibleTooltipButton>;
}, }
}); }

View file

@ -17,49 +17,44 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import Tinter from "../../../Tinter"; import Tinter from "../../../Tinter";
const TintableSvg = createReactClass({ class TintableSvg extends React.Component {
displayName: 'TintableSvg', static propTypes = {
propTypes: {
src: PropTypes.string.isRequired, src: PropTypes.string.isRequired,
width: PropTypes.string.isRequired, width: PropTypes.string.isRequired,
height: PropTypes.string.isRequired, height: PropTypes.string.isRequired,
className: PropTypes.string, className: PropTypes.string,
}, };
statics: { // list of currently mounted TintableSvgs
// list of currently mounted TintableSvgs static mounts = {};
mounts: {}, static idSequence = 0;
idSequence: 0,
},
componentDidMount: function() { componentDidMount() {
this.fixups = []; this.fixups = [];
this.id = TintableSvg.idSequence++; this.id = TintableSvg.idSequence++;
TintableSvg.mounts[this.id] = this; TintableSvg.mounts[this.id] = this;
}, }
componentWillUnmount: function() { componentWillUnmount() {
delete TintableSvg.mounts[this.id]; delete TintableSvg.mounts[this.id];
}, }
tint: function() { tint = () => {
// TODO: only bother running this if the global tint settings have changed // TODO: only bother running this if the global tint settings have changed
// since we loaded! // since we loaded!
Tinter.applySvgFixups(this.fixups); Tinter.applySvgFixups(this.fixups);
}, };
onLoad: function(event) { onLoad = event => {
// console.log("TintableSvg.onLoad for " + this.props.src); // console.log("TintableSvg.onLoad for " + this.props.src);
this.fixups = Tinter.calcSvgFixups([event.target]); this.fixups = Tinter.calcSvgFixups([event.target]);
Tinter.applySvgFixups(this.fixups); Tinter.applySvgFixups(this.fixups);
}, };
render: function() { render() {
return ( return (
<object className={"mx_TintableSvg " + (this.props.className ? this.props.className : "")} <object className={"mx_TintableSvg " + (this.props.className ? this.props.className : "")}
type="image/svg+xml" type="image/svg+xml"
@ -70,8 +65,8 @@ const TintableSvg = createReactClass({
tabIndex="-1" tabIndex="-1"
/> />
); );
}, }
}); }
// Register with the Tinter so that we will be told if the tint changes // Register with the Tinter so that we will be told if the tint changes
Tinter.registerTintable(function() { Tinter.registerTintable(function() {

View file

@ -16,31 +16,26 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
export default createReactClass({ export default class TooltipButton extends React.Component {
displayName: 'TooltipButton', state = {
hover: false,
};
getInitialState: function() { onMouseOver = () => {
return {
hover: false,
};
},
onMouseOver: function() {
this.setState({ this.setState({
hover: true, hover: true,
}); });
}, };
onMouseLeave: function() { onMouseLeave = () => {
this.setState({ this.setState({
hover: false, hover: false,
}); });
}, };
render: function() { render() {
const Tooltip = sdk.getComponent("elements.Tooltip"); const Tooltip = sdk.getComponent("elements.Tooltip");
const tip = this.state.hover ? <Tooltip const tip = this.state.hover ? <Tooltip
className="mx_TooltipButton_container" className="mx_TooltipButton_container"
@ -53,5 +48,5 @@ export default createReactClass({
{ tip } { tip }
</div> </div>
); );
}, }
}); }

View file

@ -17,13 +17,10 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
export default createReactClass({ export default class TruncatedList extends React.Component {
displayName: 'TruncatedList', static propTypes = {
propTypes: {
// The number of elements to show before truncating. If negative, no truncation is done. // The number of elements to show before truncating. If negative, no truncation is done.
truncateAt: PropTypes.number, truncateAt: PropTypes.number,
// The className to apply to the wrapping div // The className to apply to the wrapping div
@ -40,20 +37,18 @@ export default createReactClass({
// A function which will be invoked when an overflow element is required. // A function which will be invoked when an overflow element is required.
// This will be inserted after the children. // This will be inserted after the children.
createOverflowElement: PropTypes.func, createOverflowElement: PropTypes.func,
}, };
getDefaultProps: function() { static defaultProps ={
return { truncateAt: 2,
truncateAt: 2, createOverflowElement(overflowCount, totalCount) {
createOverflowElement: function(overflowCount, totalCount) { return (
return ( <div>{ _t("And %(count)s more...", {count: overflowCount}) }</div>
<div>{ _t("And %(count)s more...", {count: overflowCount}) }</div> );
); },
}, };
};
},
_getChildren: function(start, end) { _getChildren(start, end) {
if (this.props.getChildren && this.props.getChildCount) { if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildren(start, end); return this.props.getChildren(start, end);
} else { } else {
@ -64,9 +59,9 @@ export default createReactClass({
return c != null; return c != null;
}).slice(start, end); }).slice(start, end);
} }
}, }
_getChildCount: function() { _getChildCount() {
if (this.props.getChildren && this.props.getChildCount) { if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildCount(); return this.props.getChildCount();
} else { } else {
@ -74,9 +69,9 @@ export default createReactClass({
return c != null; return c != null;
}).length; }).length;
} }
}, }
render: function() { render() {
let overflowNode = null; let overflowNode = null;
const totalChildren = this._getChildCount(); const totalChildren = this._getChildCount();
@ -98,5 +93,5 @@ export default createReactClass({
{ overflowNode } { overflowNode }
</div> </div>
); );
}, }
}); }

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import {_t} from '../../../languageHandler'; import {_t} from '../../../languageHandler';
@ -29,50 +28,48 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex";
// XXX this class copies a lot from RoomTile.js // XXX this class copies a lot from RoomTile.js
export default createReactClass({ export default class GroupInviteTile extends React.Component {
displayName: 'GroupInviteTile', static propTypes: {
propTypes: {
group: PropTypes.object.isRequired, group: PropTypes.object.isRequired,
}, };
statics: { static contextType = MatrixClientContext;
contextType: MatrixClientContext,
},
getInitialState: function() { constructor(props) {
return ({ super(props);
this.state = {
hover: false, hover: false,
badgeHover: false, badgeHover: false,
menuDisplayed: false, menuDisplayed: false,
selected: this.props.group.groupId === null, // XXX: this needs linking to LoggedInView/GroupView state selected: this.props.group.groupId === null, // XXX: this needs linking to LoggedInView/GroupView state
}); };
}, }
onClick: function(e) { onClick = e => {
dis.dispatch({ dis.dispatch({
action: 'view_group', action: 'view_group',
group_id: this.props.group.groupId, group_id: this.props.group.groupId,
}); });
}, };
onMouseEnter: function() { onMouseEnter = () => {
const state = {hover: true}; const state = {hover: true};
// Only allow non-guests to access the context menu // Only allow non-guests to access the context menu
if (!this.context.isGuest()) { if (!this.context.isGuest()) {
state.badgeHover = true; state.badgeHover = true;
} }
this.setState(state); this.setState(state);
}, };
onMouseLeave: function() { onMouseLeave = () => {
this.setState({ this.setState({
badgeHover: false, badgeHover: false,
hover: false, hover: false,
}); });
}, };
_showContextMenu: function(boundingClientRect) { _showContextMenu(boundingClientRect) {
// Only allow non-guests to access the context menu // Only allow non-guests to access the context menu
if (MatrixClientPeg.get().isGuest()) return; if (MatrixClientPeg.get().isGuest()) return;
@ -86,17 +83,17 @@ export default createReactClass({
} }
this.setState(state); this.setState(state);
}, }
onContextMenuButtonClick: function(e) { onContextMenuButtonClick = e => {
// Prevent the RoomTile onClick event firing as well // Prevent the RoomTile onClick event firing as well
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this._showContextMenu(e.target.getBoundingClientRect()); this._showContextMenu(e.target.getBoundingClientRect());
}, };
onContextMenu: function(e) { onContextMenu = e => {
// Prevent the native context menu // Prevent the native context menu
e.preventDefault(); e.preventDefault();
@ -105,15 +102,15 @@ export default createReactClass({
top: e.clientY, top: e.clientY,
height: 0, height: 0,
}); });
}, };
closeMenu: function() { closeMenu = () => {
this.setState({ this.setState({
contextMenuPosition: null, contextMenuPosition: null,
}); });
}, };
render: function() { render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
@ -197,5 +194,5 @@ export default createReactClass({
{ contextMenu } { contextMenu }
</React.Fragment>; </React.Fragment>;
}, }
}); }

View file

@ -16,7 +16,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
@ -30,33 +29,29 @@ import {Action} from "../../../dispatcher/actions";
const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_MEMBERS = 30;
export default createReactClass({ export default class GroupMemberList extends React.Component {
displayName: 'GroupMemberList', static propTypes = {
propTypes: {
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
}, };
getInitialState: function() { state = {
return { members: null,
members: null, membersError: null,
membersError: null, invitedMembers: null,
invitedMembers: null, invitedMembersError: null,
invitedMembersError: null, truncateAt: INITIAL_LOAD_NUM_MEMBERS,
truncateAt: INITIAL_LOAD_NUM_MEMBERS, };
};
},
componentDidMount: function() { componentDidMount() {
this._unmounted = false; this._unmounted = false;
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}, }
componentWillUnmount: function() { componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
}, }
_initGroupStore: function(groupId) { _initGroupStore(groupId) {
GroupStore.registerListener(groupId, () => { GroupStore.registerListener(groupId, () => {
this._fetchMembers(); this._fetchMembers();
}); });
@ -73,17 +68,17 @@ export default createReactClass({
}); });
} }
}); });
}, }
_fetchMembers: function() { _fetchMembers() {
if (this._unmounted) return; if (this._unmounted) return;
this.setState({ this.setState({
members: GroupStore.getGroupMembers(this.props.groupId), members: GroupStore.getGroupMembers(this.props.groupId),
invitedMembers: GroupStore.getGroupInvitedMembers(this.props.groupId), invitedMembers: GroupStore.getGroupInvitedMembers(this.props.groupId),
}); });
}, }
_createOverflowTile: function(overflowCount, totalCount) { _createOverflowTile = (overflowCount, totalCount) => {
// For now we'll pretend this is any entity. It should probably be a separate tile. // For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile"); const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -94,19 +89,19 @@ export default createReactClass({
} name={text} presenceState="online" suppressOnHover={true} } name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullMemberList} /> onClick={this._showFullMemberList} />
); );
}, };
_showFullMemberList: function() { _showFullMemberList = () => {
this.setState({ this.setState({
truncateAt: -1, truncateAt: -1,
}); });
}, };
onSearchQueryChanged: function(ev) { onSearchQueryChanged = ev => {
this.setState({ searchQuery: ev.target.value }); this.setState({ searchQuery: ev.target.value });
}, };
makeGroupMemberTiles: function(query, memberList, memberListError) { makeGroupMemberTiles(query, memberList, memberListError) {
if (memberListError) { if (memberListError) {
return <div className="warning">{ _t("Failed to load group members") }</div>; return <div className="warning">{ _t("Failed to load group members") }</div>;
} }
@ -160,9 +155,9 @@ export default createReactClass({
> >
{ memberTiles } { memberTiles }
</TruncatedList>; </TruncatedList>;
}, }
onInviteToGroupButtonClick() { onInviteToGroupButtonClick = () => {
showGroupInviteDialog(this.props.groupId).then(() => { showGroupInviteDialog(this.props.groupId).then(() => {
dis.dispatch({ dis.dispatch({
action: Action.SetRightPanelPhase, action: Action.SetRightPanelPhase,
@ -170,9 +165,9 @@ export default createReactClass({
refireParams: { groupId: this.props.groupId }, refireParams: { groupId: this.props.groupId },
}); });
}); });
}, };
render: function() { render() {
if (this.state.fetching || this.state.fetchingInvitedMembers) { if (this.state.fetching || this.state.fetchingInvitedMembers) {
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
return (<div className="mx_MemberList"> return (<div className="mx_MemberList">
@ -230,5 +225,5 @@ export default createReactClass({
{ inputBox } { inputBox }
</div> </div>
); );
}, }
}); }

View file

@ -18,37 +18,28 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import { GroupMemberType } from '../../../groups'; import { GroupMemberType } from '../../../groups';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({ export default class GroupMemberTile extends React.Component {
displayName: 'GroupMemberTile', static propTypes = {
propTypes: {
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
member: GroupMemberType.isRequired, member: GroupMemberType.isRequired,
}, };
getInitialState: function() { static contextType = MatrixClientContext;
return {};
},
statics: { onClick = e => {
contextType: MatrixClientContext,
},
onClick: function(e) {
dis.dispatch({ dis.dispatch({
action: 'view_group_user', action: 'view_group_user',
member: this.props.member, member: this.props.member,
groupId: this.props.groupId, groupId: this.props.groupId,
}); });
}, };
render: function() { render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile'); const EntityTile = sdk.getComponent('rooms.EntityTile');
@ -74,5 +65,5 @@ export default createReactClass({
powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null} powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null}
/> />
); );
}, }
}); }

View file

@ -16,44 +16,39 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import GroupStore from '../../../stores/GroupStore'; import GroupStore from '../../../stores/GroupStore';
import ToggleSwitch from "../elements/ToggleSwitch"; import ToggleSwitch from "../elements/ToggleSwitch";
export default createReactClass({ export default class GroupPublicityToggle extends React.Component {
displayName: 'GroupPublicityToggle', static propTypes = {
propTypes: {
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
}, };
getInitialState() { state = {
return { busy: false,
busy: false, ready: false,
ready: false, isGroupPublicised: false, // assume false as <ToggleSwitch /> expects a boolean
isGroupPublicised: false, // assume false as <ToggleSwitch /> expects a boolean };
};
},
componentDidMount: function() { componentDidMount() {
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}, }
_initGroupStore: function(groupId) { _initGroupStore(groupId) {
this._groupStoreToken = GroupStore.registerListener(groupId, () => { this._groupStoreToken = GroupStore.registerListener(groupId, () => {
this.setState({ this.setState({
isGroupPublicised: Boolean(GroupStore.getGroupPublicity(groupId)), isGroupPublicised: Boolean(GroupStore.getGroupPublicity(groupId)),
ready: GroupStore.isStateReady(groupId, GroupStore.STATE_KEY.Summary), ready: GroupStore.isStateReady(groupId, GroupStore.STATE_KEY.Summary),
}); });
}); });
}, }
componentWillUnmount() { componentWillUnmount() {
if (this._groupStoreToken) this._groupStoreToken.unregister(); if (this._groupStoreToken) this._groupStoreToken.unregister();
}, }
_onPublicityToggle: function() { _onPublicityToggle = () => {
this.setState({ this.setState({
busy: true, busy: true,
// Optimistic early update // Optimistic early update
@ -64,7 +59,7 @@ export default createReactClass({
busy: false, busy: false,
}); });
}); });
}, };
render() { render() {
const GroupTile = sdk.getComponent('groups.GroupTile'); const GroupTile = sdk.getComponent('groups.GroupTile');
@ -76,5 +71,5 @@ export default createReactClass({
disabled={!this.state.ready || this.state.busy} disabled={!this.state.ready || this.state.busy}
onChange={this._onPublicityToggle} /> onChange={this._onPublicityToggle} />
</div>; </div>;
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -26,30 +25,24 @@ import GroupStore from '../../../stores/GroupStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
export default createReactClass({ export default class GroupRoomInfo extends React.Component {
displayName: 'GroupRoomInfo', static contextType = MatrixClientContext;
statics: { static propTypes = {
contextType: MatrixClientContext,
},
propTypes: {
groupId: PropTypes.string, groupId: PropTypes.string,
groupRoomId: PropTypes.string, groupRoomId: PropTypes.string,
}, };
getInitialState: function() { state = {
return { isUserPrivilegedInGroup: null,
isUserPrivilegedInGroup: null, groupRoom: null,
groupRoom: null, groupRoomPublicityLoading: false,
groupRoomPublicityLoading: false, groupRoomRemoveLoading: false,
groupRoomRemoveLoading: false, };
};
},
componentDidMount: function() { componentDidMount() {
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(newProps) { UNSAFE_componentWillReceiveProps(newProps) {
@ -57,19 +50,19 @@ export default createReactClass({
this._unregisterGroupStore(this.props.groupId); this._unregisterGroupStore(this.props.groupId);
this._initGroupStore(newProps.groupId); this._initGroupStore(newProps.groupId);
} }
}, }
componentWillUnmount() { componentWillUnmount() {
this._unregisterGroupStore(this.props.groupId); this._unregisterGroupStore(this.props.groupId);
}, }
_initGroupStore(groupId) { _initGroupStore(groupId) {
GroupStore.registerListener(groupId, this.onGroupStoreUpdated); GroupStore.registerListener(groupId, this.onGroupStoreUpdated);
}, }
_unregisterGroupStore(groupId) { _unregisterGroupStore(groupId) {
GroupStore.unregisterListener(this.onGroupStoreUpdated); GroupStore.unregisterListener(this.onGroupStoreUpdated);
}, }
_updateGroupRoom() { _updateGroupRoom() {
this.setState({ this.setState({
@ -77,16 +70,16 @@ export default createReactClass({
(r) => r.roomId === this.props.groupRoomId, (r) => r.roomId === this.props.groupRoomId,
), ),
}); });
}, }
onGroupStoreUpdated: function() { onGroupStoreUpdated = () => {
this.setState({ this.setState({
isUserPrivilegedInGroup: GroupStore.isUserPrivileged(this.props.groupId), isUserPrivilegedInGroup: GroupStore.isUserPrivileged(this.props.groupId),
}); });
this._updateGroupRoom(); this._updateGroupRoom();
}, };
_onRemove: function(e) { _onRemove = e => {
const groupId = this.props.groupId; const groupId = this.props.groupId;
const roomName = this.state.groupRoom.displayname; const roomName = this.state.groupRoom.displayname;
e.preventDefault(); e.preventDefault();
@ -119,15 +112,15 @@ export default createReactClass({
}); });
}, },
}); });
}, };
_onCancel: function(e) { _onCancel = e => {
dis.dispatch({ dis.dispatch({
action: "view_group_room_list", action: "view_group_room_list",
}); });
}, };
_changeGroupRoomPublicity(e) { _changeGroupRoomPublicity = e => {
const isPublic = e.target.value === "public"; const isPublic = e.target.value === "public";
this.setState({ this.setState({
groupRoomPublicityLoading: true, groupRoomPublicityLoading: true,
@ -150,9 +143,9 @@ export default createReactClass({
groupRoomPublicityLoading: false, groupRoomPublicityLoading: false,
}); });
}); });
}, };
render: function() { render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) { if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) {
@ -235,5 +228,5 @@ export default createReactClass({
</AutoHideScrollbar> </AutoHideScrollbar>
</div> </div>
); );
}, }
}); }

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import GroupStore from '../../../stores/GroupStore'; import GroupStore from '../../../stores/GroupStore';
@ -25,34 +24,32 @@ import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
const INITIAL_LOAD_NUM_ROOMS = 30; const INITIAL_LOAD_NUM_ROOMS = 30;
export default createReactClass({ export default class GroupRoomList extends React.Component {
propTypes: { static propTypes = {
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
}, };
getInitialState: function() { state = {
return { rooms: null,
rooms: null, truncateAt: INITIAL_LOAD_NUM_ROOMS,
truncateAt: INITIAL_LOAD_NUM_ROOMS, searchQuery: "",
searchQuery: "", };
};
},
componentDidMount: function() { componentDidMount() {
this._unmounted = false; this._unmounted = false;
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}, }
componentWillUnmount() { componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
this._unregisterGroupStore(); this._unregisterGroupStore();
}, }
_unregisterGroupStore() { _unregisterGroupStore() {
GroupStore.unregisterListener(this.onGroupStoreUpdated); GroupStore.unregisterListener(this.onGroupStoreUpdated);
}, }
_initGroupStore: function(groupId) { _initGroupStore(groupId) {
GroupStore.registerListener(groupId, this.onGroupStoreUpdated); GroupStore.registerListener(groupId, this.onGroupStoreUpdated);
// XXX: This should be more fluxy - let's get the error from GroupStore .getError or something // XXX: This should be more fluxy - let's get the error from GroupStore .getError or something
// XXX: This is also leaked - we should remove it when unmounting // XXX: This is also leaked - we should remove it when unmounting
@ -62,16 +59,16 @@ export default createReactClass({
rooms: null, rooms: null,
}); });
}); });
}, }
onGroupStoreUpdated: function() { onGroupStoreUpdated = () => {
if (this._unmounted) return; if (this._unmounted) return;
this.setState({ this.setState({
rooms: GroupStore.getGroupRooms(this.props.groupId), rooms: GroupStore.getGroupRooms(this.props.groupId),
}); });
}, };
_createOverflowTile: function(overflowCount, totalCount) { _createOverflowTile = (overflowCount, totalCount) => {
// For now we'll pretend this is any entity. It should probably be a separate tile. // For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile"); const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -82,25 +79,25 @@ export default createReactClass({
} name={text} presenceState="online" suppressOnHover={true} } name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullRoomList} /> onClick={this._showFullRoomList} />
); );
}, };
_showFullRoomList: function() { _showFullRoomList = () => {
this.setState({ this.setState({
truncateAt: -1, truncateAt: -1,
}); });
}, };
onSearchQueryChanged: function(ev) { onSearchQueryChanged = ev => {
this.setState({ searchQuery: ev.target.value }); this.setState({ searchQuery: ev.target.value });
}, };
onAddRoomToGroupButtonClick() { onAddRoomToGroupButtonClick = () => {
showGroupAddRoomDialog(this.props.groupId).then(() => { showGroupAddRoomDialog(this.props.groupId).then(() => {
this.forceUpdate(); this.forceUpdate();
}); });
}, };
makeGroupRoomTiles: function(query) { makeGroupRoomTiles(query) {
const GroupRoomTile = sdk.getComponent("groups.GroupRoomTile"); const GroupRoomTile = sdk.getComponent("groups.GroupRoomTile");
query = (query || "").toLowerCase(); query = (query || "").toLowerCase();
@ -123,9 +120,9 @@ export default createReactClass({
}); });
return roomList; return roomList;
}, }
render: function() { render() {
if (this.state.rooms === null) { if (this.state.rooms === null) {
return null; return null;
} }
@ -160,5 +157,5 @@ export default createReactClass({
{ inputBox } { inputBox }
</div> </div>
); );
}, }
}); }

View file

@ -16,29 +16,28 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import { GroupRoomType } from '../../../groups'; import { GroupRoomType } from '../../../groups';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
const GroupRoomTile = createReactClass({ class GroupRoomTile extends React.Component {
displayName: 'GroupRoomTile', static propTypes = {
propTypes: {
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
groupRoom: GroupRoomType.isRequired, groupRoom: GroupRoomType.isRequired,
}, };
onClick: function(e) { static contextType = MatrixClientContext
onClick = e => {
dis.dispatch({ dis.dispatch({
action: 'view_group_room', action: 'view_group_room',
groupId: this.props.groupId, groupId: this.props.groupId,
groupRoomId: this.props.groupRoom.roomId, groupRoomId: this.props.groupRoom.roomId,
}); });
}, };
render: function() { render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const avatarUrl = this.context.mxcUrlToHttp( const avatarUrl = this.context.mxcUrlToHttp(
@ -63,10 +62,7 @@ const GroupRoomTile = createReactClass({
</div> </div>
</AccessibleButton> </AccessibleButton>
); );
}, }
}); }
GroupRoomTile.contextType = MatrixClientContext;
export default GroupRoomTile; export default GroupRoomTile;

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { Draggable, Droppable } from 'react-beautiful-dnd'; import { Draggable, Droppable } from 'react-beautiful-dnd';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
@ -25,53 +24,45 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
function nop() {} function nop() {}
const GroupTile = createReactClass({ class GroupTile extends React.Component {
displayName: 'GroupTile', static propTypes = {
propTypes: {
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
// Whether to show the short description of the group on the tile // Whether to show the short description of the group on the tile
showDescription: PropTypes.bool, showDescription: PropTypes.bool,
// Height of the group avatar in pixels // Height of the group avatar in pixels
avatarHeight: PropTypes.number, avatarHeight: PropTypes.number,
draggable: PropTypes.bool, draggable: PropTypes.bool,
}, };
statics: { static contextType = MatrixClientContext;
contextType: MatrixClientContext,
},
getInitialState() { static defaultProps = {
return { showDescription: true,
profile: null, avatarHeight: 50,
}; draggable: true,
}, };
getDefaultProps() { state = {
return { profile: null,
showDescription: true, };
avatarHeight: 50,
draggable: true,
};
},
componentDidMount: function() { componentDidMount() {
FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => { FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => {
this.setState({profile}); this.setState({profile});
}).catch((err) => { }).catch((err) => {
console.error('Error whilst getting cached profile for GroupTile', err); console.error('Error whilst getting cached profile for GroupTile', err);
}); });
}, }
onMouseDown: function(e) { onMouseDown = e => {
e.preventDefault(); e.preventDefault();
dis.dispatch({ dis.dispatch({
action: 'view_group', action: 'view_group',
group_id: this.props.groupId, group_id: this.props.groupId,
}); });
}, };
render: function() { render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const profile = this.state.profile || {}; const profile = this.state.profile || {};
@ -135,7 +126,7 @@ const GroupTile = createReactClass({
<div className="mx_GroupTile_groupId">{ this.props.groupId }</div> <div className="mx_GroupTile_groupId">{ this.props.groupId }</div>
</div> </div>
</AccessibleButton>; </AccessibleButton>;
}, }
}); }
export default GroupTile; export default GroupTile;

View file

@ -15,33 +15,26 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({ export default class GroupUserSettings extends React.Component {
displayName: 'GroupUserSettings', static contextType = MatrixClientContext;
statics: { state = {
contextType: MatrixClientContext, error: null,
}, groups: null,
};
getInitialState() { componentDidMount() {
return {
error: null,
groups: null,
};
},
componentDidMount: function() {
this.context.getJoinedGroups().then((result) => { this.context.getJoinedGroups().then((result) => {
this.setState({groups: result.groups || [], error: null}); this.setState({groups: result.groups || [], error: null});
}, (err) => { }, (err) => {
console.error(err); console.error(err);
this.setState({groups: null, error: err}); this.setState({groups: null, error: err});
}); });
}, }
render() { render() {
let text = ""; let text = "";
@ -70,5 +63,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import filesize from 'filesize'; import filesize from 'filesize';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -117,16 +116,8 @@ function computedStyle(element) {
return cssText; return cssText;
} }
export default createReactClass({ export default class MFileBody extends React.Component {
displayName: 'MFileBody', static propTypes = {
getInitialState: function() {
return {
decryptedBlob: (this.props.decryptedBlob ? this.props.decryptedBlob : null),
};
},
propTypes: {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
/* already decrypted blob */ /* already decrypted blob */
@ -135,7 +126,19 @@ export default createReactClass({
onHeightChanged: PropTypes.func, onHeightChanged: PropTypes.func,
/* the shape of the tile, used */ /* the shape of the tile, used */
tileShape: PropTypes.string, tileShape: PropTypes.string,
}, };
constructor(props) {
super(props);
this.state = {
decryptedBlob: (this.props.decryptedBlob ? this.props.decryptedBlob : null),
};
this._iframe = createRef();
this._dummyLink = createRef();
this._downloadImage = createRef();
}
/** /**
* Extracts a human readable label for the file attachment to use as * Extracts a human readable label for the file attachment to use as
@ -144,7 +147,7 @@ export default createReactClass({
* @params {Object} content The "content" key of the matrix event. * @params {Object} content The "content" key of the matrix event.
* @return {string} the human readable link text for the attachment. * @return {string} the human readable link text for the attachment.
*/ */
presentableTextForFile: function(content) { presentableTextForFile(content) {
let linkText = _t("Attachment"); let linkText = _t("Attachment");
if (content.body && content.body.length > 0) { if (content.body && content.body.length > 0) {
// The content body should be the name of the file including a // The content body should be the name of the file including a
@ -163,40 +166,33 @@ export default createReactClass({
linkText += ' (' + filesize(content.info.size) + ')'; linkText += ' (' + filesize(content.info.size) + ')';
} }
return linkText; return linkText;
}, }
_getContentUrl: function() { _getContentUrl() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
return MatrixClientPeg.get().mxcUrlToHttp(content.url); return MatrixClientPeg.get().mxcUrlToHttp(content.url);
}, }
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs componentDidMount() {
UNSAFE_componentWillMount: function() {
this._iframe = createRef();
this._dummyLink = createRef();
this._downloadImage = createRef();
},
componentDidMount: function() {
// Add this to the list of mounted components to receive notifications // Add this to the list of mounted components to receive notifications
// when the tint changes. // when the tint changes.
this.id = nextMountId++; this.id = nextMountId++;
mounts[this.id] = this; mounts[this.id] = this;
this.tint(); this.tint();
}, }
componentDidUpdate: function(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) { if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) {
this.props.onHeightChanged(); this.props.onHeightChanged();
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
// Remove this from the list of mounted components // Remove this from the list of mounted components
delete mounts[this.id]; delete mounts[this.id];
}, }
tint: function() { tint = () => {
// Update our tinted copy of require("../../../../res/img/download.svg") // Update our tinted copy of require("../../../../res/img/download.svg")
if (this._downloadImage.current) { if (this._downloadImage.current) {
this._downloadImage.current.src = tintedDownloadImageURL; this._downloadImage.current.src = tintedDownloadImageURL;
@ -210,9 +206,9 @@ export default createReactClass({
style: computedStyle(this._dummyLink.current), style: computedStyle(this._dummyLink.current),
}, "*"); }, "*");
} }
}, };
render: function() { render() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
const text = this.presentableTextForFile(content); const text = this.presentableTextForFile(content);
const isEncrypted = content.file !== undefined; const isEncrypted = content.file !== undefined;
@ -378,5 +374,5 @@ export default createReactClass({
{ _t("Invalid file%(extra)s", { extra: extra }) } { _t("Invalid file%(extra)s", { extra: extra }) }
</span>; </span>;
} }
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import MFileBody from './MFileBody'; import MFileBody from './MFileBody';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { decryptFile } from '../../../utils/DecryptFile'; import { decryptFile } from '../../../utils/DecryptFile';
@ -25,27 +24,23 @@ import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import InlineSpinner from '../elements/InlineSpinner'; import InlineSpinner from '../elements/InlineSpinner';
export default createReactClass({ export default class MVideoBody extends React.Component {
displayName: 'MVideoBody', static propTypes = {
propTypes: {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
/* called when the video has loaded */ /* called when the video has loaded */
onHeightChanged: PropTypes.func.isRequired, onHeightChanged: PropTypes.func.isRequired,
}, };
getInitialState: function() { state = {
return { decryptedUrl: null,
decryptedUrl: null, decryptedThumbnailUrl: null,
decryptedThumbnailUrl: null, decryptedBlob: null,
decryptedBlob: null, error: null,
error: null, };
};
},
thumbScale: function(fullWidth, fullHeight, thumbWidth, thumbHeight) { thumbScale(fullWidth, fullHeight, thumbWidth, thumbHeight) {
if (!fullWidth || !fullHeight) { if (!fullWidth || !fullHeight) {
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
// log this because it's spammy // log this because it's spammy
@ -64,18 +59,18 @@ export default createReactClass({
// height is the dominant dimension so scaling will be fixed on that // height is the dominant dimension so scaling will be fixed on that
return heightMulti; return heightMulti;
} }
}, }
_getContentUrl: function() { _getContentUrl() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined) { if (content.file !== undefined) {
return this.state.decryptedUrl; return this.state.decryptedUrl;
} else { } else {
return MatrixClientPeg.get().mxcUrlToHttp(content.url); return MatrixClientPeg.get().mxcUrlToHttp(content.url);
} }
}, }
_getThumbUrl: function() { _getThumbUrl() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined) { if (content.file !== undefined) {
return this.state.decryptedThumbnailUrl; return this.state.decryptedThumbnailUrl;
@ -84,9 +79,9 @@ export default createReactClass({
} else { } else {
return null; return null;
} }
}, }
componentDidMount: function() { componentDidMount() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) { if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null); let thumbnailPromise = Promise.resolve(null);
@ -118,18 +113,18 @@ export default createReactClass({
}); });
}); });
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
if (this.state.decryptedUrl) { if (this.state.decryptedUrl) {
URL.revokeObjectURL(this.state.decryptedUrl); URL.revokeObjectURL(this.state.decryptedUrl);
} }
if (this.state.decryptedThumbnailUrl) { if (this.state.decryptedThumbnailUrl) {
URL.revokeObjectURL(this.state.decryptedThumbnailUrl); URL.revokeObjectURL(this.state.decryptedThumbnailUrl);
} }
}, }
render: function() { render() {
const content = this.props.mxEvent.getContent(); const content = this.props.mxEvent.getContent();
if (this.state.error !== null) { if (this.state.error !== null) {
@ -182,5 +177,5 @@ export default createReactClass({
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} /> <MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
</span> </span>
); );
}, }
}); }

View file

@ -16,17 +16,14 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {Mjolnir} from "../../../mjolnir/Mjolnir"; import {Mjolnir} from "../../../mjolnir/Mjolnir";
import RedactedBody from "./RedactedBody"; import RedactedBody from "./RedactedBody";
import UnknownBody from "./UnknownBody"; import UnknownBody from "./UnknownBody";
export default createReactClass({ export default class MessageEvent extends React.Component {
displayName: 'MessageEvent', static propTypes = {
propTypes: {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
@ -47,22 +44,23 @@ export default createReactClass({
/* the maximum image height to use, if the event is an image */ /* the maximum image height to use, if the event is an image */
maxImageHeight: PropTypes.number, maxImageHeight: PropTypes.number,
}, };
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._body = createRef(); this._body = createRef();
}, }
getEventTileOps: function() { getEventTileOps = () => {
return this._body.current && this._body.current.getEventTileOps ? this._body.current.getEventTileOps() : null; return this._body.current && this._body.current.getEventTileOps ? this._body.current.getEventTileOps() : null;
}, };
onTileUpdate: function() { onTileUpdate = () => {
this.forceUpdate(); this.forceUpdate();
}, };
render: function() { render() {
const bodyTypes = { const bodyTypes = {
'm.text': sdk.getComponent('messages.TextualBody'), 'm.text': sdk.getComponent('messages.TextualBody'),
'm.notice': sdk.getComponent('messages.TextualBody'), 'm.notice': sdk.getComponent('messages.TextualBody'),
@ -123,5 +121,5 @@ export default createReactClass({
onHeightChanged={this.props.onHeightChanged} onHeightChanged={this.props.onHeightChanged}
onMessageAllowed={this.onTileUpdate} onMessageAllowed={this.onTileUpdate}
/>; />;
}, }
}); }

View file

@ -18,22 +18,19 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
export default createReactClass({ export default class RoomAvatarEvent extends React.Component {
displayName: 'RoomAvatarEvent', static propTypes = {
propTypes: {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
}, };
onAvatarClick: function() { onAvatarClick = () => {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const ev = this.props.mxEvent; const ev = this.props.mxEvent;
const httpUrl = cli.mxcUrlToHttp(ev.getContent().url); const httpUrl = cli.mxcUrlToHttp(ev.getContent().url);
@ -50,9 +47,9 @@ export default createReactClass({
name: text, name: text,
}; };
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
}, };
render: function() { render() {
const ev = this.props.mxEvent; const ev = this.props.mxEvent;
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
@ -86,5 +83,5 @@ export default createReactClass({
} }
</div> </div>
); );
}, }
}); }

View file

@ -17,22 +17,19 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
export default createReactClass({ export default class RoomCreate extends React.Component {
displayName: 'RoomCreate', static propTypes = {
propTypes: {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
}, };
_onLinkClicked: function(e) { _onLinkClicked = e => {
e.preventDefault(); e.preventDefault();
const predecessor = this.props.mxEvent.getContent()['predecessor']; const predecessor = this.props.mxEvent.getContent()['predecessor'];
@ -43,9 +40,9 @@ export default createReactClass({
highlighted: true, highlighted: true,
room_id: predecessor['room_id'], room_id: predecessor['room_id'],
}); });
}, };
render: function() { render() {
const predecessor = this.props.mxEvent.getContent()['predecessor']; const predecessor = this.props.mxEvent.getContent()['predecessor'];
if (predecessor === undefined) { if (predecessor === undefined) {
return <div />; // We should never have been instaniated in this case return <div />; // We should never have been instaniated in this case
@ -66,5 +63,5 @@ export default createReactClass({
{_t("Click here to see older messages.")} {_t("Click here to see older messages.")}
</a> </a>
</div>; </div>;
}, }
}); }

View file

@ -16,31 +16,25 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import Flair from '../elements/Flair.js'; import Flair from '../elements/Flair.js';
import FlairStore from '../../../stores/FlairStore'; import FlairStore from '../../../stores/FlairStore';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {getUserNameColorClass} from '../../../utils/FormattingUtils'; import {getUserNameColorClass} from '../../../utils/FormattingUtils';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({ export default class SenderProfile extends React.Component {
displayName: 'SenderProfile', static propTypes = {
propTypes: {
mxEvent: PropTypes.object.isRequired, // event whose sender we're showing mxEvent: PropTypes.object.isRequired, // event whose sender we're showing
text: PropTypes.string, // Text to show. Defaults to sender name text: PropTypes.string, // Text to show. Defaults to sender name
onClick: PropTypes.func, onClick: PropTypes.func,
}, };
statics: { static contextType = MatrixClientContext;
contextType: MatrixClientContext,
},
getInitialState() { state = {
return { userGroups: null,
userGroups: null, relatedGroups: [],
relatedGroups: [], };
};
},
componentDidMount() { componentDidMount() {
this.unmounted = false; this.unmounted = false;
@ -54,20 +48,20 @@ export default createReactClass({
}); });
this.context.on('RoomState.events', this.onRoomStateEvents); this.context.on('RoomState.events', this.onRoomStateEvents);
}, }
componentWillUnmount() { componentWillUnmount() {
this.unmounted = true; this.unmounted = true;
this.context.removeListener('RoomState.events', this.onRoomStateEvents); this.context.removeListener('RoomState.events', this.onRoomStateEvents);
}, }
onRoomStateEvents(event) { onRoomStateEvents = event => {
if (event.getType() === 'm.room.related_groups' && if (event.getType() === 'm.room.related_groups' &&
event.getRoomId() === this.props.mxEvent.getRoomId() event.getRoomId() === this.props.mxEvent.getRoomId()
) { ) {
this._updateRelatedGroups(); this._updateRelatedGroups();
} }
}, };
_updateRelatedGroups() { _updateRelatedGroups() {
if (this.unmounted) return; if (this.unmounted) return;
@ -78,7 +72,7 @@ export default createReactClass({
this.setState({ this.setState({
relatedGroups: relatedGroupsEvent ? relatedGroupsEvent.getContent().groups || [] : [], relatedGroups: relatedGroupsEvent ? relatedGroupsEvent.getContent().groups || [] : [],
}); });
}, }
_getDisplayedGroups(userGroups, relatedGroups) { _getDisplayedGroups(userGroups, relatedGroups) {
let displayedGroups = userGroups || []; let displayedGroups = userGroups || [];
@ -90,7 +84,7 @@ export default createReactClass({
displayedGroups = []; displayedGroups = [];
} }
return displayedGroups; return displayedGroups;
}, }
render() { render() {
const {mxEvent} = this.props; const {mxEvent} = this.props;
@ -138,5 +132,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -19,7 +19,6 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import highlight from 'highlight.js'; import highlight from 'highlight.js';
import * as HtmlUtils from '../../../HtmlUtils'; import * as HtmlUtils from '../../../HtmlUtils';
import {formatDate} from '../../../DateUtils'; import {formatDate} from '../../../DateUtils';
@ -37,10 +36,8 @@ import {toRightOf} from "../../structures/ContextMenu";
import {copyPlaintext} from "../../../utils/strings"; import {copyPlaintext} from "../../../utils/strings";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
export default createReactClass({ export default class TextualBody extends React.Component {
displayName: 'TextualBody', static propTypes = {
propTypes: {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
@ -58,10 +55,14 @@ export default createReactClass({
/* the shape of the tile, used */ /* the shape of the tile, used */
tileShape: PropTypes.string, tileShape: PropTypes.string,
}, };
getInitialState: function() { constructor(props) {
return { super(props);
this._content = createRef();
this.state = {
// the URLs (if any) to be previewed with a LinkPreviewWidget // the URLs (if any) to be previewed with a LinkPreviewWidget
// inside this TextualBody. // inside this TextualBody.
links: [], links: [],
@ -69,20 +70,15 @@ export default createReactClass({
// track whether the preview widget is hidden // track whether the preview widget is hidden
widgetHidden: false, widgetHidden: false,
}; };
}, }
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs componentDidMount() {
UNSAFE_componentWillMount: function() {
this._content = createRef();
},
componentDidMount: function() {
this._unmounted = false; this._unmounted = false;
this._pills = []; this._pills = [];
if (!this.props.editState) { if (!this.props.editState) {
this._applyFormatting(); this._applyFormatting();
} }
}, }
_applyFormatting() { _applyFormatting() {
this.activateSpoilers([this._content.current]); this.activateSpoilers([this._content.current]);
@ -119,9 +115,9 @@ export default createReactClass({
} }
this._addCodeCopyButton(); this._addCodeCopyButton();
} }
}, }
componentDidUpdate: function(prevProps) { componentDidUpdate(prevProps) {
if (!this.props.editState) { if (!this.props.editState) {
const stoppedEditing = prevProps.editState && !this.props.editState; const stoppedEditing = prevProps.editState && !this.props.editState;
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId; const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
@ -129,14 +125,14 @@ export default createReactClass({
this._applyFormatting(); this._applyFormatting();
} }
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
this._unmounted = true; this._unmounted = true;
unmountPills(this._pills); unmountPills(this._pills);
}, }
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
//console.info("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview); //console.info("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
// exploit that events are immutable :) // exploit that events are immutable :)
@ -148,9 +144,9 @@ export default createReactClass({
nextProps.editState !== this.props.editState || nextProps.editState !== this.props.editState ||
nextState.links !== this.state.links || nextState.links !== this.state.links ||
nextState.widgetHidden !== this.state.widgetHidden); nextState.widgetHidden !== this.state.widgetHidden);
}, }
calculateUrlPreview: function() { calculateUrlPreview() {
//console.info("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview); //console.info("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
if (this.props.showUrlPreview) { if (this.props.showUrlPreview) {
@ -176,9 +172,9 @@ export default createReactClass({
this.setState({ links: [] }); this.setState({ links: [] });
} }
} }
}, }
activateSpoilers: function(nodes) { activateSpoilers(nodes) {
let node = nodes[0]; let node = nodes[0];
while (node) { while (node) {
if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") { if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") {
@ -204,9 +200,9 @@ export default createReactClass({
node = node.nextSibling; node = node.nextSibling;
} }
}, }
findLinks: function(nodes) { findLinks(nodes) {
let links = []; let links = [];
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
@ -223,9 +219,9 @@ export default createReactClass({
} }
} }
return links; return links;
}, }
isLinkPreviewable: function(node) { isLinkPreviewable(node) {
// don't try to preview relative links // don't try to preview relative links
if (!node.getAttribute("href").startsWith("http://") && if (!node.getAttribute("href").startsWith("http://") &&
!node.getAttribute("href").startsWith("https://")) { !node.getAttribute("href").startsWith("https://")) {
@ -256,7 +252,7 @@ export default createReactClass({
return true; return true;
} }
} }
}, }
_addCodeCopyButton() { _addCodeCopyButton() {
// Add 'copy' buttons to pre blocks // Add 'copy' buttons to pre blocks
@ -288,41 +284,39 @@ export default createReactClass({
div.appendChild(p); div.appendChild(p);
div.appendChild(button); div.appendChild(button);
}); });
}, }
onCancelClick: function(event) { onCancelClick = event => {
this.setState({ widgetHidden: true }); this.setState({ widgetHidden: true });
// FIXME: persist this somewhere smarter than local storage // FIXME: persist this somewhere smarter than local storage
if (global.localStorage) { if (global.localStorage) {
global.localStorage.setItem("hide_preview_" + this.props.mxEvent.getId(), "1"); global.localStorage.setItem("hide_preview_" + this.props.mxEvent.getId(), "1");
} }
this.forceUpdate(); this.forceUpdate();
}, };
onEmoteSenderClick: function(event) { onEmoteSenderClick = event => {
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
dis.dispatch({ dis.dispatch({
action: 'insert_mention', action: 'insert_mention',
user_id: mxEvent.getSender(), user_id: mxEvent.getSender(),
}); });
}, };
getEventTileOps: function() { getEventTileOps = () => ({
return { isWidgetHidden: () => {
isWidgetHidden: () => { return this.state.widgetHidden;
return this.state.widgetHidden; },
},
unhideWidget: () => { unhideWidget: () => {
this.setState({ widgetHidden: false }); this.setState({widgetHidden: false});
if (global.localStorage) { if (global.localStorage) {
global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId()); global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
} }
}, },
}; });
},
onStarterLinkClick: function(starterLink, ev) { onStarterLinkClick = (starterLink, ev) => {
ev.preventDefault(); ev.preventDefault();
// We need to add on our scalar token to the starter link, but we may not have one! // We need to add on our scalar token to the starter link, but we may not have one!
// In addition, we can't fetch one on click and then go to it immediately as that // In addition, we can't fetch one on click and then go to it immediately as that
@ -353,7 +347,7 @@ export default createReactClass({
"Do you wish to continue?", { integrationsUrl: integrationsUrl }) } "Do you wish to continue?", { integrationsUrl: integrationsUrl }) }
</div>, </div>,
button: _t("Continue"), button: _t("Continue"),
onFinished: function(confirmed) { onFinished(confirmed) {
if (!confirmed) { if (!confirmed) {
return; return;
} }
@ -367,14 +361,14 @@ export default createReactClass({
}, },
}); });
}); });
}, };
_openHistoryDialog: async function() { _openHistoryDialog = async () => {
const MessageEditHistoryDialog = sdk.getComponent("views.dialogs.MessageEditHistoryDialog"); const MessageEditHistoryDialog = sdk.getComponent("views.dialogs.MessageEditHistoryDialog");
Modal.createDialog(MessageEditHistoryDialog, {mxEvent: this.props.mxEvent}); Modal.createDialog(MessageEditHistoryDialog, {mxEvent: this.props.mxEvent});
}, };
_renderEditedMarker: function() { _renderEditedMarker() {
const date = this.props.mxEvent.replacingEventDate(); const date = this.props.mxEvent.replacingEventDate();
const dateString = date && formatDate(date); const dateString = date && formatDate(date);
@ -397,9 +391,9 @@ export default createReactClass({
<span>{`(${_t("edited")})`}</span> <span>{`(${_t("edited")})`}</span>
</AccessibleTooltipButton> </AccessibleTooltipButton>
); );
}, }
render: function() { render() {
if (this.props.editState) { if (this.props.editState) {
const EditMessageComposer = sdk.getComponent('rooms.EditMessageComposer'); const EditMessageComposer = sdk.getComponent('rooms.EditMessageComposer');
return <EditMessageComposer editState={this.props.editState} className="mx_EventTile_content" />; return <EditMessageComposer editState={this.props.editState} className="mx_EventTile_content" />;
@ -468,5 +462,5 @@ export default createReactClass({
</span> </span>
); );
} }
}, }
}); }

View file

@ -17,22 +17,19 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as TextForEvent from "../../../TextForEvent"; import * as TextForEvent from "../../../TextForEvent";
export default createReactClass({ export default class TextualEvent extends React.Component {
displayName: 'TextualEvent', static propTypes = {
propTypes: {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
}, };
render: function() { render() {
const text = TextForEvent.textForEvent(this.props.mxEvent); const text = TextForEvent.textForEvent(this.props.mxEvent);
if (text == null || text.length === 0) return null; if (text == null || text.length === 0) return null;
return ( return (
<div className="mx_TextualEvent">{ text }</div> <div className="mx_TextualEvent">{ text }</div>
); );
}, }
}); }

View file

@ -19,7 +19,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
@ -29,20 +28,18 @@ import {Action} from "../../../dispatcher/actions";
import {SettingLevel} from "../../../settings/SettingLevel"; import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({ export default class UrlPreviewSettings extends React.Component {
displayName: 'UrlPreviewSettings', static propTypes = {
propTypes: {
room: PropTypes.object, room: PropTypes.object,
}, };
_onClickUserSettings: (e) => { _onClickUserSettings = (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
dis.fire(Action.ViewUserSettings); dis.fire(Action.ViewUserSettings);
}, };
render: function() { render() {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag"); const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
const roomId = this.props.room.roomId; const roomId = this.props.room.roomId;
const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId); const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
@ -110,5 +107,5 @@ export default createReactClass({
<label>{ previewsForRoomAccount }</label> <label>{ previewsForRoomAccount }</label>
</div> </div>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import AppTile from '../elements/AppTile'; import AppTile from '../elements/AppTile';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
@ -34,50 +33,50 @@ import SettingsStore from "../../../settings/SettingsStore";
// The maximum number of widgets that can be added in a room // The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2; const MAX_WIDGETS = 2;
export default createReactClass({ export default class AppsDrawer extends React.Component {
displayName: 'AppsDrawer', static propTypes = {
propTypes: {
userId: PropTypes.string.isRequired, userId: PropTypes.string.isRequired,
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,
showApps: PropTypes.bool, // Should apps be rendered showApps: PropTypes.bool, // Should apps be rendered
hide: PropTypes.bool, // If rendered, should apps drawer be visible hide: PropTypes.bool, // If rendered, should apps drawer be visible
}, };
getDefaultProps: () => ({ static defaultProps = {
showApps: true, showApps: true,
hide: false, hide: false,
}), };
getInitialState: function() { constructor(props) {
return { super(props);
this.state = {
apps: this._getApps(), apps: this._getApps(),
}; };
}, }
componentDidMount: function() { componentDidMount() {
ScalarMessaging.startListening(); ScalarMessaging.startListening();
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
WidgetEchoStore.on('update', this._updateApps); WidgetEchoStore.on('update', this._updateApps);
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
}, }
componentWillUnmount: function() { componentWillUnmount() {
ScalarMessaging.stopListening(); ScalarMessaging.stopListening();
if (MatrixClientPeg.get()) { if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
} }
WidgetEchoStore.removeListener('update', this._updateApps); WidgetEchoStore.removeListener('update', this._updateApps);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef); if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(newProps) { UNSAFE_componentWillReceiveProps(newProps) {
// Room has changed probably, update apps // Room has changed probably, update apps
this._updateApps(); this._updateApps();
}, }
onAction: function(action) { onAction = (action) => {
const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer'; const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer';
switch (action.action) { switch (action.action) {
case 'appsDrawer': case 'appsDrawer':
@ -93,16 +92,16 @@ export default createReactClass({
break; break;
} }
}, };
onRoomStateEvents: function(ev, state) { onRoomStateEvents = (ev, state) => {
if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') { if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') {
return; return;
} }
this._updateApps(); this._updateApps();
}, };
_getApps: function() { _getApps() {
const widgets = WidgetEchoStore.getEchoedRoomWidgets( const widgets = WidgetEchoStore.getEchoedRoomWidgets(
this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room), this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
); );
@ -111,33 +110,33 @@ export default createReactClass({
ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(), ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(),
); );
}); });
}, }
_updateApps: function() { _updateApps = () => {
const apps = this._getApps(); const apps = this._getApps();
this.setState({ this.setState({
apps: apps, apps: apps,
}); });
}, };
_canUserModify: function() { _canUserModify() {
try { try {
return WidgetUtils.canUserModifyWidgets(this.props.room.roomId); return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
return false; return false;
} }
}, }
_launchManageIntegrations: function() { _launchManageIntegrations() {
if (SettingsStore.getValue("feature_many_integration_managers")) { if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll(); IntegrationManagers.sharedInstance().openAll();
} else { } else {
IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ'); IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ');
} }
}, }
onClickAddWidget: function(e) { onClickAddWidget = (e) => {
e.preventDefault(); e.preventDefault();
// Display a warning dialog if the max number of widgets have already been added to the room // Display a warning dialog if the max number of widgets have already been added to the room
const apps = this._getApps(); const apps = this._getApps();
@ -152,9 +151,9 @@ export default createReactClass({
return; return;
} }
this._launchManageIntegrations(); this._launchManageIntegrations();
}, };
render: function() { render() {
const apps = this.state.apps.map((app, index, arr) => { const apps = this.state.apps.map((app, index, arr) => {
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId); const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId);
@ -211,5 +210,5 @@ export default createReactClass({
{ this._canUserModify() && addWidget } { this._canUserModify() && addWidget }
</div> </div>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@ -31,10 +30,8 @@ import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import CallView from "../voip/CallView"; import CallView from "../voip/CallView";
export default createReactClass({ export default class AuxPanel extends React.Component {
displayName: 'AuxPanel', static propTypes = {
propTypes: {
// js-sdk room object // js-sdk room object
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,
userId: PropTypes.string.isRequired, userId: PropTypes.string.isRequired,
@ -58,42 +55,46 @@ export default createReactClass({
// content in a way that is likely to make it change size. // content in a way that is likely to make it change size.
onResize: PropTypes.func, onResize: PropTypes.func,
fullHeight: PropTypes.bool, fullHeight: PropTypes.bool,
}, };
getDefaultProps: () => ({ static defaultProps = {
showApps: true, showApps: true,
hideAppsDrawer: false, hideAppsDrawer: false,
}), };
getInitialState: function() { constructor(props) {
return { counters: this._computeCounters() }; super(props);
},
componentDidMount: function() { this.state = {
counters: this._computeCounters(),
};
}
componentDidMount() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
cli.on("RoomState.events", this._rateLimitedUpdate); cli.on("RoomState.events", this._rateLimitedUpdate);
}, }
componentWillUnmount: function() { componentWillUnmount() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (cli) { if (cli) {
cli.removeListener("RoomState.events", this._rateLimitedUpdate); cli.removeListener("RoomState.events", this._rateLimitedUpdate);
} }
}, }
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return (!ObjectUtils.shallowEqual(this.props, nextProps) || return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
!ObjectUtils.shallowEqual(this.state, nextState)); !ObjectUtils.shallowEqual(this.state, nextState));
}, }
componentDidUpdate: function(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
// most changes are likely to cause a resize // most changes are likely to cause a resize
if (this.props.onResize) { if (this.props.onResize) {
this.props.onResize(); this.props.onResize();
} }
}, }
onConferenceNotificationClick: function(ev, type) { onConferenceNotificationClick = (ev, type) => {
dis.dispatch({ dis.dispatch({
action: 'place_call', action: 'place_call',
type: type, type: type,
@ -101,15 +102,15 @@ export default createReactClass({
}); });
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
}, };
_rateLimitedUpdate: new RateLimitedFunc(function() { _rateLimitedUpdate = new RateLimitedFunc(() => {
if (SettingsStore.getValue("feature_state_counters")) { if (SettingsStore.getValue("feature_state_counters")) {
this.setState({counters: this._computeCounters()}); this.setState({counters: this._computeCounters()});
} }
}, 500), }, 500);
_computeCounters: function() { _computeCounters() {
let counters = []; let counters = [];
if (this.props.room && SettingsStore.getValue("feature_state_counters")) { if (this.props.room && SettingsStore.getValue("feature_state_counters")) {
@ -140,9 +141,9 @@ export default createReactClass({
} }
return counters; return counters;
}, }
render: function() { render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
let fileDropTarget = null; let fileDropTarget = null;
@ -274,5 +275,5 @@ export default createReactClass({
{ this.props.children } { this.props.children }
</AutoHideScrollbar> </AutoHideScrollbar>
); );
}, }
}); }

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -51,10 +50,8 @@ function presenceClassForMember(presenceState, lastActiveAgo, showPresence) {
} }
} }
const EntityTile = createReactClass({ class EntityTile extends React.Component {
displayName: 'EntityTile', static propTypes = {
propTypes: {
name: PropTypes.string, name: PropTypes.string,
title: PropTypes.string, title: PropTypes.string,
avatarJsx: PropTypes.any, // <BaseAvatar /> avatarJsx: PropTypes.any, // <BaseAvatar />
@ -70,33 +67,29 @@ const EntityTile = createReactClass({
showPresence: PropTypes.bool, showPresence: PropTypes.bool,
subtextLabel: PropTypes.string, subtextLabel: PropTypes.string,
e2eStatus: PropTypes.string, e2eStatus: PropTypes.string,
}, };
getDefaultProps: function() { static defaultProps = {
return { shouldComponentUpdate: function(nextProps, nextState) { return true; },
shouldComponentUpdate: function(nextProps, nextState) { return true; }, onClick: function() {},
onClick: function() {}, presenceState: "offline",
presenceState: "offline", presenceLastActiveAgo: 0,
presenceLastActiveAgo: 0, presenceLastTs: 0,
presenceLastTs: 0, showInviteButton: false,
showInviteButton: false, suppressOnHover: false,
suppressOnHover: false, showPresence: true,
showPresence: true, };
};
},
getInitialState: function() { state = {
return { hover: false,
hover: false, };
};
},
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
if (this.state.hover !== nextState.hover) return true; if (this.state.hover !== nextState.hover) return true;
return this.props.shouldComponentUpdate(nextProps, nextState); return this.props.shouldComponentUpdate(nextProps, nextState);
}, }
render: function() { render() {
const mainClassNames = { const mainClassNames = {
"mx_EntityTile": true, "mx_EntityTile": true,
"mx_EntityTile_noHover": this.props.suppressOnHover, "mx_EntityTile_noHover": this.props.suppressOnHover,
@ -193,8 +186,8 @@ const EntityTile = createReactClass({
</AccessibleButton> </AccessibleButton>
</div> </div>
); );
}, }
}); }
EntityTile.POWER_STATUS_MODERATOR = "moderator"; EntityTile.POWER_STATUS_MODERATOR = "moderator";
EntityTile.POWER_STATUS_ADMIN = "admin"; EntityTile.POWER_STATUS_ADMIN = "admin";

View file

@ -20,7 +20,6 @@ limitations under the License.
import ReplyThread from "../elements/ReplyThread"; import ReplyThread from "../elements/ReplyThread";
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from "classnames"; import classNames from "classnames";
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import * as TextForEvent from "../../../TextForEvent"; import * as TextForEvent from "../../../TextForEvent";
@ -127,10 +126,8 @@ const MAX_READ_AVATARS = 5;
// | '--------------------------------------' | // | '--------------------------------------' |
// '----------------------------------------------------------' // '----------------------------------------------------------'
export default createReactClass({ export default class EventTile extends React.Component {
displayName: 'EventTile', static propTypes = {
propTypes: {
/* the MatrixEvent to show */ /* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
@ -209,17 +206,19 @@ export default createReactClass({
// whether to use the irc layout // whether to use the irc layout
useIRCLayout: PropTypes.bool, useIRCLayout: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { // no-op function because onHeightChanged is optional yet some sub-components assume its existence
// no-op function because onHeightChanged is optional yet some sub-components assume its existence onHeightChanged: function() {},
onHeightChanged: function() {}, };
};
},
getInitialState: function() { static contextType = MatrixClientContext;
return {
constructor(props) {
super(props);
this.state = {
// Whether the action bar is focused. // Whether the action bar is focused.
actionBarFocused: false, actionBarFocused: false,
// Whether all read receipts are being displayed. If not, only display // Whether all read receipts are being displayed. If not, only display
@ -232,23 +231,16 @@ export default createReactClass({
// The Relations model from the JS SDK for reactions to `mxEvent` // The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(), reactions: this.getReactions(),
}; };
},
statics: {
contextType: MatrixClientContext,
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
// don't do RR animations until we are mounted // don't do RR animations until we are mounted
this._suppressReadReceiptAnimation = true; this._suppressReadReceiptAnimation = true;
this._verifyEvent(this.props.mxEvent); this._verifyEvent(this.props.mxEvent);
this._tile = createRef(); this._tile = createRef();
this._replyThread = createRef(); this._replyThread = createRef();
}, }
componentDidMount: function() { componentDidMount() {
this._suppressReadReceiptAnimation = false; this._suppressReadReceiptAnimation = false;
const client = this.context; const client = this.context;
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged); client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
@ -257,26 +249,26 @@ export default createReactClass({
if (this.props.showReactions) { if (this.props.showReactions) {
this.props.mxEvent.on("Event.relationsCreated", this._onReactionsCreated); this.props.mxEvent.on("Event.relationsCreated", this._onReactionsCreated);
} }
}, }
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event // TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(nextProps) { UNSAFE_componentWillReceiveProps(nextProps) {
// re-check the sender verification as outgoing events progress through // re-check the sender verification as outgoing events progress through
// the send process. // the send process.
if (nextProps.eventSendStatus !== this.props.eventSendStatus) { if (nextProps.eventSendStatus !== this.props.eventSendStatus) {
this._verifyEvent(nextProps.mxEvent); this._verifyEvent(nextProps.mxEvent);
} }
}, }
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
if (!ObjectUtils.shallowEqual(this.state, nextState)) { if (!ObjectUtils.shallowEqual(this.state, nextState)) {
return true; return true;
} }
return !this._propsEqual(this.props, nextProps); return !this._propsEqual(this.props, nextProps);
}, }
componentWillUnmount: function() { componentWillUnmount() {
const client = this.context; const client = this.context;
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
@ -284,31 +276,31 @@ export default createReactClass({
if (this.props.showReactions) { if (this.props.showReactions) {
this.props.mxEvent.removeListener("Event.relationsCreated", this._onReactionsCreated); this.props.mxEvent.removeListener("Event.relationsCreated", this._onReactionsCreated);
} }
}, }
/** called when the event is decrypted after we show it. /** called when the event is decrypted after we show it.
*/ */
_onDecrypted: function() { _onDecrypted = () => {
// we need to re-verify the sending device. // we need to re-verify the sending device.
// (we call onHeightChanged in _verifyEvent to handle the case where decryption // (we call onHeightChanged in _verifyEvent to handle the case where decryption
// has caused a change in size of the event tile) // has caused a change in size of the event tile)
this._verifyEvent(this.props.mxEvent); this._verifyEvent(this.props.mxEvent);
this.forceUpdate(); this.forceUpdate();
}, };
onDeviceVerificationChanged: function(userId, device) { onDeviceVerificationChanged = (userId, device) => {
if (userId === this.props.mxEvent.getSender()) { if (userId === this.props.mxEvent.getSender()) {
this._verifyEvent(this.props.mxEvent); this._verifyEvent(this.props.mxEvent);
} }
}, };
onUserVerificationChanged: function(userId, _trustStatus) { onUserVerificationChanged = (userId, _trustStatus) => {
if (userId === this.props.mxEvent.getSender()) { if (userId === this.props.mxEvent.getSender()) {
this._verifyEvent(this.props.mxEvent); this._verifyEvent(this.props.mxEvent);
} }
}, };
_verifyEvent: async function(mxEvent) { async _verifyEvent(mxEvent) {
if (!mxEvent.isEncrypted()) { if (!mxEvent.isEncrypted()) {
return; return;
} }
@ -360,9 +352,9 @@ export default createReactClass({
this.setState({ this.setState({
verified: E2E_STATE.VERIFIED, verified: E2E_STATE.VERIFIED,
}, this.props.onHeightChanged); // Decryption may have caused a change in size }, this.props.onHeightChanged); // Decryption may have caused a change in size
}, }
_propsEqual: function(objA, objB) { _propsEqual(objA, objB) {
const keysA = Object.keys(objA); const keysA = Object.keys(objA);
const keysB = Object.keys(objB); const keysB = Object.keys(objB);
@ -408,9 +400,9 @@ export default createReactClass({
} }
} }
return true; return true;
}, }
shouldHighlight: function() { shouldHighlight() {
const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent); const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; } if (!actions || !actions.tweaks) { return false; }
@ -420,15 +412,15 @@ export default createReactClass({
} }
return actions.tweaks.highlight; return actions.tweaks.highlight;
}, }
toggleAllReadAvatars: function() { toggleAllReadAvatars = () => {
this.setState({ this.setState({
allReadAvatars: !this.state.allReadAvatars, allReadAvatars: !this.state.allReadAvatars,
}); });
}, };
getReadAvatars: function() { getReadAvatars() {
// return early if there are no read receipts // return early if there are no read receipts
if (!this.props.readReceipts || this.props.readReceipts.length === 0) { if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
return (<span className="mx_EventTile_readAvatars" />); return (<span className="mx_EventTile_readAvatars" />);
@ -494,17 +486,17 @@ export default createReactClass({
{ remText } { remText }
{ avatars } { avatars }
</span>; </span>;
}, }
onSenderProfileClick: function(event) { onSenderProfileClick = event => {
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
dis.dispatch({ dis.dispatch({
action: 'insert_mention', action: 'insert_mention',
user_id: mxEvent.getSender(), user_id: mxEvent.getSender(),
}); });
}, };
onRequestKeysClick: function() { onRequestKeysClick = () => {
this.setState({ this.setState({
// Indicate in the UI that the keys have been requested (this is expected to // Indicate in the UI that the keys have been requested (this is expected to
// be reset if the component is mounted in the future). // be reset if the component is mounted in the future).
@ -515,9 +507,9 @@ export default createReactClass({
// is received for the request with the required keys, the event could be // is received for the request with the required keys, the event could be
// decrypted successfully. // decrypted successfully.
this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent); this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
}, };
onPermalinkClicked: function(e) { onPermalinkClicked = e => {
// This allows the permalink to be opened in a new tab/window or copied as // This allows the permalink to be opened in a new tab/window or copied as
// matrix.to, but also for it to enable routing within Element when clicked. // matrix.to, but also for it to enable routing within Element when clicked.
e.preventDefault(); e.preventDefault();
@ -527,9 +519,9 @@ export default createReactClass({
highlighted: true, highlighted: true,
room_id: this.props.mxEvent.getRoomId(), room_id: this.props.mxEvent.getRoomId(),
}); });
}, };
_renderE2EPadlock: function() { _renderE2EPadlock() {
const ev = this.props.mxEvent; const ev = this.props.mxEvent;
// event could not be decrypted // event could not be decrypted
@ -570,23 +562,19 @@ export default createReactClass({
// no padlock needed // no padlock needed
return null; return null;
}, }
onActionBarFocusChange(focused) { onActionBarFocusChange = focused => {
this.setState({ this.setState({
actionBarFocused: focused, actionBarFocused: focused,
}); });
}, };
getTile() { getTile = () => this._tile.current;
return this._tile.current;
},
getReplyThread() { getReplyThread = () => this._replyThread.current;
return this._replyThread.current;
},
getReactions() { getReactions = () => {
if ( if (
!this.props.showReactions || !this.props.showReactions ||
!this.props.getRelationsForEvent !this.props.getRelationsForEvent
@ -602,9 +590,9 @@ export default createReactClass({
console.trace("Stacktrace for https://github.com/vector-im/element-web/issues/11120"); console.trace("Stacktrace for https://github.com/vector-im/element-web/issues/11120");
} }
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction"); return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction");
}, };
_onReactionsCreated(relationType, eventType) { _onReactionsCreated = (relationType, eventType) => {
if (relationType !== "m.annotation" || eventType !== "m.reaction") { if (relationType !== "m.annotation" || eventType !== "m.reaction") {
return; return;
} }
@ -612,9 +600,9 @@ export default createReactClass({
this.setState({ this.setState({
reactions: this.getReactions(), reactions: this.getReactions(),
}); });
}, };
render: function() { render() {
const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp'); const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
const SenderProfile = sdk.getComponent('messages.SenderProfile'); const SenderProfile = sdk.getComponent('messages.SenderProfile');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
@ -947,8 +935,8 @@ export default createReactClass({
); );
} }
} }
}, }
}); }
// XXX this'll eventually be dynamic based on the fields once we have extensible event types // XXX this'll eventually be dynamic based on the fields once we have extensible event types
const messageTypes = ['m.room.message', 'm.sticker']; const messageTypes = ['m.room.message', 'm.sticker'];

View file

@ -17,49 +17,46 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import {Key} from '../../../Keyboard'; import {Key} from '../../../Keyboard';
export default createReactClass({ export default class ForwardMessage extends React.Component {
displayName: 'ForwardMessage', static propTypes = {
propTypes: {
onCancelClick: PropTypes.func.isRequired, onCancelClick: PropTypes.func.isRequired,
}, };
componentDidMount: function() { componentDidMount() {
dis.dispatch({ dis.dispatch({
action: 'panel_disable', action: 'panel_disable',
middleDisabled: true, middleDisabled: true,
}); });
document.addEventListener('keydown', this._onKeyDown); document.addEventListener('keydown', this._onKeyDown);
}, }
componentWillUnmount: function() { componentWillUnmount() {
dis.dispatch({ dis.dispatch({
action: 'panel_disable', action: 'panel_disable',
middleDisabled: false, middleDisabled: false,
}); });
document.removeEventListener('keydown', this._onKeyDown); document.removeEventListener('keydown', this._onKeyDown);
}, }
_onKeyDown: function(ev) { _onKeyDown = ev => {
switch (ev.key) { switch (ev.key) {
case Key.ESCAPE: case Key.ESCAPE:
this.props.onCancelClick(); this.props.onCancelClick();
break; break;
} }
}, };
render: function() { render() {
return ( return (
<div className="mx_ForwardMessage"> <div className="mx_ForwardMessage">
<h1>{ _t('Please select the destination room for this message') }</h1> <h1>{ _t('Please select the destination room for this message') }</h1>
</div> </div>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { AllHtmlEntities } from 'html-entities'; import { AllHtmlEntities } from 'html-entities';
import {linkifyElement} from '../../../HtmlUtils'; import {linkifyElement} from '../../../HtmlUtils';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
@ -27,24 +26,21 @@ import Modal from "../../../Modal";
import * as ImageUtils from "../../../ImageUtils"; import * as ImageUtils from "../../../ImageUtils";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
export default createReactClass({ export default class LinkPreviewWidget extends React.Component {
displayName: 'LinkPreviewWidget', static propTypes = {
propTypes: {
link: PropTypes.string.isRequired, // the URL being previewed link: PropTypes.string.isRequired, // the URL being previewed
mxEvent: PropTypes.object.isRequired, // the Event associated with the preview mxEvent: PropTypes.object.isRequired, // the Event associated with the preview
onCancelClick: PropTypes.func, // called when the preview's cancel ('hide') button is clicked onCancelClick: PropTypes.func, // called when the preview's cancel ('hide') button is clicked
onHeightChanged: PropTypes.func, // called when the preview's contents has loaded onHeightChanged: PropTypes.func, // called when the preview's contents has loaded
}, };
getInitialState: function() { constructor(props) {
return { super(props);
this.state = {
preview: null, preview: null,
}; };
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this.unmounted = false; this.unmounted = false;
MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.mxEvent.getTs()).then((res)=>{ MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.mxEvent.getTs()).then((res)=>{
if (this.unmounted) { if (this.unmounted) {
@ -59,25 +55,25 @@ export default createReactClass({
}); });
this._description = createRef(); this._description = createRef();
}, }
componentDidMount: function() { componentDidMount() {
if (this._description.current) { if (this._description.current) {
linkifyElement(this._description.current); linkifyElement(this._description.current);
} }
}, }
componentDidUpdate: function() { componentDidUpdate() {
if (this._description.current) { if (this._description.current) {
linkifyElement(this._description.current); linkifyElement(this._description.current);
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
this.unmounted = true; this.unmounted = true;
}, }
onImageClick: function(ev) { onImageClick = ev => {
const p = this.state.preview; const p = this.state.preview;
if (ev.button != 0 || ev.metaKey) return; if (ev.button != 0 || ev.metaKey) return;
ev.preventDefault(); ev.preventDefault();
@ -98,9 +94,9 @@ export default createReactClass({
}; };
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
}, };
render: function() { render() {
const p = this.state.preview; const p = this.state.preview;
if (!p || Object.keys(p).length === 0) { if (!p || Object.keys(p).length === 0) {
return <div />; return <div />;
@ -149,5 +145,5 @@ export default createReactClass({
</AccessibleButton> </AccessibleButton>
</div> </div>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
@ -36,23 +35,19 @@ const SHOW_MORE_INCREMENT = 100;
// matches all ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ // matches all ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
const SORT_REGEX = /[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/g; const SORT_REGEX = /[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/g;
export default createReactClass({ export default class MemberList extends React.Component {
displayName: 'MemberList', constructor(props) {
super(props);
getInitialState: function() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) { if (cli.hasLazyLoadMembersEnabled()) {
// show an empty list // show an empty list
return this._getMembersState([]); this.state = this._getMembersState([]);
} else { } else {
return this._getMembersState(this.roomMembers()); this.state = this._getMembersState(this.roomMembers());
} }
},
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._mounted = true; this._mounted = true;
const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) { if (cli.hasLazyLoadMembersEnabled()) {
this._showMembersAccordingToMembershipWithLL(); this._showMembersAccordingToMembershipWithLL();
cli.on("Room.myMembership", this.onMyMembership); cli.on("Room.myMembership", this.onMyMembership);
@ -66,9 +61,9 @@ export default createReactClass({
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) { if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
this._showPresence = enablePresenceByHsUrl[hsUrl]; this._showPresence = enablePresenceByHsUrl[hsUrl];
} }
}, }
_listenForMembersChanges: function() { _listenForMembersChanges() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
cli.on("RoomState.members", this.onRoomStateMember); cli.on("RoomState.members", this.onRoomStateMember);
cli.on("RoomMember.name", this.onRoomMemberName); cli.on("RoomMember.name", this.onRoomMemberName);
@ -80,9 +75,9 @@ export default createReactClass({
cli.on("User.presence", this.onUserPresenceChange); cli.on("User.presence", this.onUserPresenceChange);
cli.on("User.currentlyActive", this.onUserPresenceChange); cli.on("User.currentlyActive", this.onUserPresenceChange);
// cli.on("Room.timeline", this.onRoomTimeline); // cli.on("Room.timeline", this.onRoomTimeline);
}, }
componentWillUnmount: function() { componentWillUnmount() {
this._mounted = false; this._mounted = false;
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (cli) { if (cli) {
@ -98,14 +93,14 @@ export default createReactClass({
// cancel any pending calls to the rate_limited_funcs // cancel any pending calls to the rate_limited_funcs
this._updateList.cancelPendingCall(); this._updateList.cancelPendingCall();
}, }
/** /**
* If lazy loading is enabled, either: * If lazy loading is enabled, either:
* show a spinner and load the members if the user is joined, * show a spinner and load the members if the user is joined,
* or show the members available so far if the user is invited * or show the members available so far if the user is invited
*/ */
_showMembersAccordingToMembershipWithLL: async function() { async _showMembersAccordingToMembershipWithLL() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) { if (cli.hasLazyLoadMembersEnabled()) {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -125,9 +120,9 @@ export default createReactClass({
this.setState(this._getMembersState(this.roomMembers())); this.setState(this._getMembersState(this.roomMembers()));
} }
} }
}, }
_getMembersState: function(members) { _getMembersState(members) {
// set the state after determining _showPresence to make sure it's // set the state after determining _showPresence to make sure it's
// taken into account while rerendering // taken into account while rerendering
return { return {
@ -142,9 +137,9 @@ export default createReactClass({
truncateAtInvited: INITIAL_LOAD_NUM_INVITED, truncateAtInvited: INITIAL_LOAD_NUM_INVITED,
searchQuery: "", searchQuery: "",
}; };
}, }
onUserPresenceChange(event, user) { onUserPresenceChange = (event, user) => {
// Attach a SINGLE listener for global presence changes then locate the // Attach a SINGLE listener for global presence changes then locate the
// member tile and re-render it. This is more efficient than every tile // member tile and re-render it. This is more efficient than every tile
// ever attaching their own listener. // ever attaching their own listener.
@ -153,9 +148,9 @@ export default createReactClass({
if (tile) { if (tile) {
this._updateList(); // reorder the membership list this._updateList(); // reorder the membership list
} }
}, };
onRoom: function(room) { onRoom = room => {
if (room.roomId !== this.props.roomId) { if (room.roomId !== this.props.roomId) {
return; return;
} }
@ -163,40 +158,40 @@ export default createReactClass({
// we need to wait till the room is fully populated with state // we need to wait till the room is fully populated with state
// before refreshing the member list else we get a stale list. // before refreshing the member list else we get a stale list.
this._showMembersAccordingToMembershipWithLL(); this._showMembersAccordingToMembershipWithLL();
}, };
onMyMembership: function(room, membership, oldMembership) { onMyMembership = (room, membership, oldMembership) => {
if (room.roomId === this.props.roomId && membership === "join") { if (room.roomId === this.props.roomId && membership === "join") {
this._showMembersAccordingToMembershipWithLL(); this._showMembersAccordingToMembershipWithLL();
} }
}, };
onRoomStateMember: function(ev, state, member) { onRoomStateMember = (ev, state, member) => {
if (member.roomId !== this.props.roomId) { if (member.roomId !== this.props.roomId) {
return; return;
} }
this._updateList(); this._updateList();
}, };
onRoomMemberName: function(ev, member) { onRoomMemberName = (ev, member) => {
if (member.roomId !== this.props.roomId) { if (member.roomId !== this.props.roomId) {
return; return;
} }
this._updateList(); this._updateList();
}, };
onRoomStateEvent: function(event, state) { onRoomStateEvent = (event, state) => {
if (event.getRoomId() === this.props.roomId && if (event.getRoomId() === this.props.roomId &&
event.getType() === "m.room.third_party_invite") { event.getType() === "m.room.third_party_invite") {
this._updateList(); this._updateList();
} }
}, };
_updateList: rate_limited_func(function() { _updateList = rate_limited_func(() => {
this._updateListNow(); this._updateListNow();
}, 500), }, 500);
_updateListNow: function() { _updateListNow() {
// console.log("Updating memberlist"); // console.log("Updating memberlist");
const newState = { const newState = {
loading: false, loading: false,
@ -205,9 +200,9 @@ export default createReactClass({
newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join', this.state.searchQuery); newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join', this.state.searchQuery);
newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite', this.state.searchQuery); newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite', this.state.searchQuery);
this.setState(newState); this.setState(newState);
}, }
getMembersWithUser: function() { getMembersWithUser() {
if (!this.props.roomId) return []; if (!this.props.roomId) return [];
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.roomId); const room = cli.getRoom(this.props.roomId);
@ -228,9 +223,9 @@ export default createReactClass({
}); });
return allMembers; return allMembers;
}, }
roomMembers: function() { roomMembers() {
const ConferenceHandler = CallHandler.getConferenceHandler(); const ConferenceHandler = CallHandler.getConferenceHandler();
const allMembers = this.getMembersWithUser(); const allMembers = this.getMembersWithUser();
@ -244,17 +239,17 @@ export default createReactClass({
}); });
filteredAndSortedMembers.sort(this.memberSort); filteredAndSortedMembers.sort(this.memberSort);
return filteredAndSortedMembers; return filteredAndSortedMembers;
}, }
_createOverflowTileJoined: function(overflowCount, totalCount) { _createOverflowTileJoined(overflowCount, totalCount) {
return this._createOverflowTile(overflowCount, totalCount, this._showMoreJoinedMemberList); return this._createOverflowTile(overflowCount, totalCount, this._showMoreJoinedMemberList);
}, }
_createOverflowTileInvited: function(overflowCount, totalCount) { _createOverflowTileInvited(overflowCount, totalCount) {
return this._createOverflowTile(overflowCount, totalCount, this._showMoreInvitedMemberList); return this._createOverflowTile(overflowCount, totalCount, this._showMoreInvitedMemberList);
}, }
_createOverflowTile: function(overflowCount, totalCount, onClick) { _createOverflowTile(overflowCount, totalCount, onClick) {
// For now we'll pretend this is any entity. It should probably be a separate tile. // For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile"); const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -265,33 +260,33 @@ export default createReactClass({
} name={text} presenceState="online" suppressOnHover={true} } name={text} presenceState="online" suppressOnHover={true}
onClick={onClick} /> onClick={onClick} />
); );
}, }
_showMoreJoinedMemberList: function() { _showMoreJoinedMemberList = () => {
this.setState({ this.setState({
truncateAtJoined: this.state.truncateAtJoined + SHOW_MORE_INCREMENT, truncateAtJoined: this.state.truncateAtJoined + SHOW_MORE_INCREMENT,
}); });
}, };
_showMoreInvitedMemberList: function() { _showMoreInvitedMemberList = () => {
this.setState({ this.setState({
truncateAtInvited: this.state.truncateAtInvited + SHOW_MORE_INCREMENT, truncateAtInvited: this.state.truncateAtInvited + SHOW_MORE_INCREMENT,
}); });
}, };
memberString: function(member) { memberString(member) {
if (!member) { if (!member) {
return "(null)"; return "(null)";
} else { } else {
const u = member.user; const u = member.user;
return "(" + member.name + ", " + member.powerLevel + ", " + (u ? u.lastActiveAgo : "<null>") + ", " + (u ? u.getLastActiveTs() : "<null>") + ", " + (u ? u.currentlyActive : "<null>") + ", " + (u ? u.presence : "<null>") + ")"; return "(" + member.name + ", " + member.powerLevel + ", " + (u ? u.lastActiveAgo : "<null>") + ", " + (u ? u.getLastActiveTs() : "<null>") + ", " + (u ? u.currentlyActive : "<null>") + ", " + (u ? u.presence : "<null>") + ")";
} }
}, }
// returns negative if a comes before b, // returns negative if a comes before b,
// returns 0 if a and b are equivalent in ordering // returns 0 if a and b are equivalent in ordering
// returns positive if a comes after b. // returns positive if a comes after b.
memberSort: function(memberA, memberB) { memberSort = (memberA, memberB) => {
// order by presence, with "active now" first. // order by presence, with "active now" first.
// ...and then by power level // ...and then by power level
// ...and then by last active // ...and then by last active
@ -348,24 +343,24 @@ export default createReactClass({
ignorePunctuation: true, ignorePunctuation: true,
sensitivity: "base", sensitivity: "base",
}); });
}, };
onSearchQueryChanged: function(searchQuery) { onSearchQueryChanged = searchQuery => {
this.setState({ this.setState({
searchQuery, searchQuery,
filteredJoinedMembers: this._filterMembers(this.state.members, 'join', searchQuery), filteredJoinedMembers: this._filterMembers(this.state.members, 'join', searchQuery),
filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', searchQuery), filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', searchQuery),
}); });
}, };
_onPending3pidInviteClick: function(inviteEvent) { _onPending3pidInviteClick = inviteEvent => {
dis.dispatch({ dis.dispatch({
action: 'view_3pid_invite', action: 'view_3pid_invite',
event: inviteEvent, event: inviteEvent,
}); });
}, };
_filterMembers: function(members, membership, query) { _filterMembers(members, membership, query) {
return members.filter((m) => { return members.filter((m) => {
if (query) { if (query) {
query = query.toLowerCase(); query = query.toLowerCase();
@ -379,9 +374,9 @@ export default createReactClass({
return m.membership === membership; return m.membership === membership;
}); });
}, }
_getPending3PidInvites: function() { _getPending3PidInvites() {
// include 3pid invites (m.room.third_party_invite) state events. // include 3pid invites (m.room.third_party_invite) state events.
// The HS may have already converted these into m.room.member invites so // The HS may have already converted these into m.room.member invites so
// we shouldn't add them if the 3pid invite state key (token) is in the // we shouldn't add them if the 3pid invite state key (token) is in the
@ -399,9 +394,9 @@ export default createReactClass({
return true; return true;
}); });
} }
}, }
_makeMemberTiles: function(members) { _makeMemberTiles(members) {
const MemberTile = sdk.getComponent("rooms.MemberTile"); const MemberTile = sdk.getComponent("rooms.MemberTile");
const EntityTile = sdk.getComponent("rooms.EntityTile"); const EntityTile = sdk.getComponent("rooms.EntityTile");
@ -415,30 +410,30 @@ export default createReactClass({
onClick={() => this._onPending3pidInviteClick(m)} />; onClick={() => this._onPending3pidInviteClick(m)} />;
} }
}); });
}, }
_getChildrenJoined: function(start, end) { _getChildrenJoined(start, end) {
return this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end)); return this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end));
}, }
_getChildCountJoined: function() { _getChildCountJoined() {
return this.state.filteredJoinedMembers.length; return this.state.filteredJoinedMembers.length;
}, }
_getChildrenInvited: function(start, end) { _getChildrenInvited(start, end) {
let targets = this.state.filteredInvitedMembers; let targets = this.state.filteredInvitedMembers;
if (end > this.state.filteredInvitedMembers.length) { if (end > this.state.filteredInvitedMembers.length) {
targets = targets.concat(this._getPending3PidInvites()); targets = targets.concat(this._getPending3PidInvites());
} }
return this._makeMemberTiles(targets.slice(start, end)); return this._makeMemberTiles(targets.slice(start, end));
}, }
_getChildCountInvited: function() { _getChildCountInvited() {
return this.state.filteredInvitedMembers.length + (this._getPending3PidInvites() || []).length; return this.state.filteredInvitedMembers.length + (this._getPending3PidInvites() || []).length;
}, }
render: function() { render() {
if (this.state.loading) { if (this.state.loading) {
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
return <div className="mx_MemberList"><Spinner /></div>; return <div className="mx_MemberList"><Spinner /></div>;
@ -501,9 +496,9 @@ export default createReactClass({
onSearch={ this.onSearchQueryChanged } /> onSearch={ this.onSearchQueryChanged } />
</div> </div>
); );
}, }
onInviteButtonClick: function() { onInviteButtonClick = () => {
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'}); dis.dispatch({action: 'require_registration'});
return; return;
@ -514,5 +509,5 @@ export default createReactClass({
action: 'view_invite', action: 'view_invite',
roomId: this.props.roomId, roomId: this.props.roomId,
}); });
}, };
}); }

View file

@ -18,34 +18,31 @@ limitations under the License.
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import {Action} from "../../../dispatcher/actions"; import {Action} from "../../../dispatcher/actions";
export default createReactClass({ export default class MemberTile extends React.Component {
displayName: 'MemberTile', static propTypes = {
propTypes: {
member: PropTypes.any.isRequired, // RoomMember member: PropTypes.any.isRequired, // RoomMember
showPresence: PropTypes.bool, showPresence: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { showPresence: true,
showPresence: true, };
};
},
getInitialState: function() { constructor(props) {
return { super(props);
this.state = {
statusMessage: this.getStatusMessage(), statusMessage: this.getStatusMessage(),
isRoomEncrypted: false, isRoomEncrypted: false,
e2eStatus: null, e2eStatus: null,
}; };
}, }
componentDidMount() { componentDidMount() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -72,7 +69,7 @@ export default createReactClass({
cli.on("RoomState.events", this.onRoomStateEvents); cli.on("RoomState.events", this.onRoomStateEvents);
} }
} }
}, }
componentWillUnmount() { componentWillUnmount() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
@ -90,9 +87,9 @@ export default createReactClass({
cli.removeListener("userTrustStatusChanged", this.onUserTrustStatusChanged); cli.removeListener("userTrustStatusChanged", this.onUserTrustStatusChanged);
cli.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); cli.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
} }
}, }
onRoomStateEvents: function(ev) { onRoomStateEvents = ev => {
if (ev.getType() !== "m.room.encryption") return; if (ev.getType() !== "m.room.encryption") return;
const { roomId } = this.props.member; const { roomId } = this.props.member;
if (ev.getRoomId() !== roomId) return; if (ev.getRoomId() !== roomId) return;
@ -104,19 +101,19 @@ export default createReactClass({
isRoomEncrypted: true, isRoomEncrypted: true,
}); });
this.updateE2EStatus(); this.updateE2EStatus();
}, };
onUserTrustStatusChanged: function(userId, trustStatus) { onUserTrustStatusChanged = (userId, trustStatus) => {
if (userId !== this.props.member.userId) return; if (userId !== this.props.member.userId) return;
this.updateE2EStatus(); this.updateE2EStatus();
}, };
onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) { onDeviceVerificationChanged = (userId, deviceId, deviceInfo) => {
if (userId !== this.props.member.userId) return; if (userId !== this.props.member.userId) return;
this.updateE2EStatus(); this.updateE2EStatus();
}, };
updateE2EStatus: async function() { async updateE2EStatus() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const { userId } = this.props.member; const { userId } = this.props.member;
const isMe = userId === cli.getUserId(); const isMe = userId === cli.getUserId();
@ -142,7 +139,7 @@ export default createReactClass({
this.setState({ this.setState({
e2eStatus: anyDeviceUnverified ? "warning" : "verified", e2eStatus: anyDeviceUnverified ? "warning" : "verified",
}); });
}, }
getStatusMessage() { getStatusMessage() {
const { user } = this.props.member; const { user } = this.props.member;
@ -150,16 +147,16 @@ export default createReactClass({
return ""; return "";
} }
return user._unstable_statusMessage; return user._unstable_statusMessage;
}, }
_onStatusMessageCommitted() { _onStatusMessageCommitted = () => {
// The `User` object has observed a status message change. // The `User` object has observed a status message change.
this.setState({ this.setState({
statusMessage: this.getStatusMessage(), statusMessage: this.getStatusMessage(),
}); });
}, };
shouldComponentUpdate: function(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
if ( if (
this.member_last_modified_time === undefined || this.member_last_modified_time === undefined ||
this.member_last_modified_time < nextProps.member.getLastModifiedTime() this.member_last_modified_time < nextProps.member.getLastModifiedTime()
@ -180,27 +177,27 @@ export default createReactClass({
return true; return true;
} }
return false; return false;
}, }
onClick: function(e) { onClick = e => {
dis.dispatch({ dis.dispatch({
action: Action.ViewUser, action: Action.ViewUser,
member: this.props.member, member: this.props.member,
}); });
}, };
_getDisplayName: function() { _getDisplayName() {
return this.props.member.name; return this.props.member.name;
}, }
getPowerLabel: function() { getPowerLabel() {
return _t("%(userName)s (power %(powerLevelNumber)s)", { return _t("%(userName)s (power %(powerLevelNumber)s)", {
userName: this.props.member.userId, userName: this.props.member.userId,
powerLevelNumber: this.props.member.powerLevel, powerLevelNumber: this.props.member.powerLevel,
}); });
}, }
render: function() { render() {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile'); const EntityTile = sdk.getComponent('rooms.EntityTile');
@ -260,5 +257,5 @@ export default createReactClass({
onClick={this.onClick} onClick={this.onClick}
/> />
); );
}, }
}); }

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from "react"; import React from "react";
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
@ -25,22 +24,23 @@ import MemberAvatar from "../avatars/MemberAvatar";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {formatFullDate} from '../../../DateUtils'; import {formatFullDate} from '../../../DateUtils';
export default createReactClass({ export default class PinnedEventTile extends React.Component {
displayName: 'PinnedEventTile', static propTypes = {
propTypes: {
mxRoom: PropTypes.object.isRequired, mxRoom: PropTypes.object.isRequired,
mxEvent: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired,
onUnpinned: PropTypes.func, onUnpinned: PropTypes.func,
}, };
onTileClicked: function() {
onTileClicked = () => {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
event_id: this.props.mxEvent.getId(), event_id: this.props.mxEvent.getId(),
highlighted: true, highlighted: true,
room_id: this.props.mxEvent.getRoomId(), room_id: this.props.mxEvent.getRoomId(),
}); });
}, };
onUnpinClicked: function() {
onUnpinClicked = () => {
const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", ""); const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", "");
if (!pinnedEvents || !pinnedEvents.getContent().pinned) { if (!pinnedEvents || !pinnedEvents.getContent().pinned) {
// Nothing to do: already unpinned // Nothing to do: already unpinned
@ -56,11 +56,13 @@ export default createReactClass({
}); });
} else if (this.props.onUnpinned) this.props.onUnpinned(); } else if (this.props.onUnpinned) this.props.onUnpinned();
} }
}, };
_canUnpin: function() {
_canUnpin() {
return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get()); return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get());
}, }
render: function() {
render() {
const sender = this.props.mxEvent.getSender(); const sender = this.props.mxEvent.getSender();
// Get the latest sender profile rather than historical // Get the latest sender profile rather than historical
const senderProfile = this.props.mxRoom.getMember(sender); const senderProfile = this.props.mxRoom.getMember(sender);
@ -100,5 +102,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -17,46 +17,42 @@ limitations under the License.
import React from "react"; import React from "react";
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import PinnedEventTile from "./PinnedEventTile"; import PinnedEventTile from "./PinnedEventTile";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import PinningUtils from "../../../utils/PinningUtils"; import PinningUtils from "../../../utils/PinningUtils";
export default createReactClass({ export default class PinnedEventsPanel extends React.Component {
displayName: 'PinnedEventsPanel', static propTypes = {
propTypes: {
// The Room from the js-sdk we're going to show pinned events for // The Room from the js-sdk we're going to show pinned events for
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,
onCancelClick: PropTypes.func, onCancelClick: PropTypes.func,
}, };
getInitialState: function() { state = {
return { loading: true,
loading: true, };
};
},
componentDidMount: function() { componentDidMount() {
this._updatePinnedMessages(); this._updatePinnedMessages();
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent); MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
}, }
componentWillUnmount: function() { componentWillUnmount() {
if (MatrixClientPeg.get()) { if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this._onStateEvent); MatrixClientPeg.get().removeListener("RoomState.events", this._onStateEvent);
} }
}, }
_onStateEvent: function(ev) { _onStateEvent = ev => {
if (ev.getRoomId() === this.props.room.roomId && ev.getType() === "m.room.pinned_events") { if (ev.getRoomId() === this.props.room.roomId && ev.getType() === "m.room.pinned_events") {
this._updatePinnedMessages(); this._updatePinnedMessages();
} }
}, };
_updatePinnedMessages: function() { _updatePinnedMessages = () => {
const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", "");
if (!pinnedEvents || !pinnedEvents.getContent().pinned) { if (!pinnedEvents || !pinnedEvents.getContent().pinned) {
this.setState({ loading: false, pinned: [] }); this.setState({ loading: false, pinned: [] });
@ -85,9 +81,9 @@ export default createReactClass({
} }
this._updateReadState(); this._updateReadState();
}, };
_updateReadState: function() { _updateReadState() {
const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", "");
if (!pinnedEvents) return; // nothing to read if (!pinnedEvents) return; // nothing to read
@ -107,9 +103,9 @@ export default createReactClass({
event_ids: readStateEvents, event_ids: readStateEvents,
}); });
} }
}, }
_getPinnedTiles: function() { _getPinnedTiles() {
if (this.state.pinned.length === 0) { if (this.state.pinned.length === 0) {
return (<div>{ _t("No pinned messages.") }</div>); return (<div>{ _t("No pinned messages.") }</div>);
} }
@ -120,9 +116,9 @@ export default createReactClass({
mxEvent={context.event} mxEvent={context.event}
onUnpinned={this._updatePinnedMessages} />); onUnpinned={this._updatePinnedMessages} />);
}); });
}, }
render: function() { render() {
let tiles = <div>{ _t("Loading...") }</div>; let tiles = <div>{ _t("Loading...") }</div>;
if (this.state && !this.state.loading) { if (this.state && !this.state.loading) {
tiles = this._getPinnedTiles(); tiles = this._getPinnedTiles();
@ -139,5 +135,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -16,15 +16,12 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
export default createReactClass({ export default class PresenceLabel extends React.Component {
displayName: 'PresenceLabel', static propTypes = {
propTypes: {
// number of milliseconds ago this user was last active. // number of milliseconds ago this user was last active.
// zero = unknown // zero = unknown
activeAgo: PropTypes.number, activeAgo: PropTypes.number,
@ -35,18 +32,16 @@ export default createReactClass({
// offline, online, etc // offline, online, etc
presenceState: PropTypes.string, presenceState: PropTypes.string,
}, };
getDefaultProps: function() { static defaultProps = {
return { activeAgo: -1,
ago: -1, presenceState: null,
presenceState: null, };
};
},
// Return duration as a string using appropriate time units // Return duration as a string using appropriate time units
// XXX: This would be better handled using a culture-aware library, but we don't use one yet. // XXX: This would be better handled using a culture-aware library, but we don't use one yet.
getDuration: function(time) { getDuration(time) {
if (!time) return; if (!time) return;
const t = parseInt(time / 1000); const t = parseInt(time / 1000);
const s = t % 60; const s = t % 60;
@ -66,9 +61,9 @@ export default createReactClass({
return _t("%(duration)sh", {duration: h}); return _t("%(duration)sh", {duration: h});
} }
return _t("%(duration)sd", {duration: d}); return _t("%(duration)sd", {duration: d});
}, }
getPrettyPresence: function(presence, activeAgo, currentlyActive) { getPrettyPresence(presence, activeAgo, currentlyActive) {
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) { if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
const duration = this.getDuration(activeAgo); const duration = this.getDuration(activeAgo);
if (presence === "online") return _t("Online for %(duration)s", { duration: duration }); if (presence === "online") return _t("Online for %(duration)s", { duration: duration });
@ -81,13 +76,13 @@ export default createReactClass({
if (presence === "offline") return _t("Offline"); if (presence === "offline") return _t("Offline");
return _t("Unknown"); return _t("Unknown");
} }
}, }
render: function() { render() {
return ( return (
<div className="mx_PresenceLabel"> <div className="mx_PresenceLabel">
{ this.getPrettyPresence(this.props.presenceState, this.props.activeAgo, this.props.currentlyActive) } { this.getPrettyPresence(this.props.presenceState, this.props.activeAgo, this.props.currentlyActive) }
</div> </div>
); );
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import '../../../VelocityBounce'; import '../../../VelocityBounce';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {formatDate} from '../../../DateUtils'; import {formatDate} from '../../../DateUtils';
@ -33,10 +32,8 @@ try {
} catch (e) { } catch (e) {
} }
export default createReactClass({ export default class ReadReceiptMarker extends React.Component {
displayName: 'ReadReceiptMarker', static propTypes = {
propTypes: {
// the RoomMember to show the RR for // the RoomMember to show the RR for
member: PropTypes.object, member: PropTypes.object,
// userId to fallback the avatar to // userId to fallback the avatar to
@ -70,30 +67,27 @@ export default createReactClass({
// True to show twelve hour format, false otherwise // True to show twelve hour format, false otherwise
showTwelveHour: PropTypes.bool, showTwelveHour: PropTypes.bool,
}, };
getDefaultProps: function() { static defaultProps = {
return { leftOffset: 0,
leftOffset: 0, };
};
},
getInitialState: function() { constructor(props) {
// if we are going to animate the RR, we don't show it on first render, super(props);
// and instead just add a placeholder to the DOM; once we've been
// mounted, we start an animation which moves the RR from its old this._avatar = createRef();
// position.
return { this.state = {
// if we are going to animate the RR, we don't show it on first render,
// and instead just add a placeholder to the DOM; once we've been
// mounted, we start an animation which moves the RR from its old
// position.
suppressDisplay: !this.props.suppressAnimation, suppressDisplay: !this.props.suppressAnimation,
}; };
}, }
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs componentWillUnmount() {
UNSAFE_componentWillMount: function() {
this._avatar = createRef();
},
componentWillUnmount: function() {
// before we remove the rr, store its location in the map, so that if // before we remove the rr, store its location in the map, so that if
// it reappears, it can be animated from the right place. // it reappears, it can be animated from the right place.
const rrInfo = this.props.readReceiptInfo; const rrInfo = this.props.readReceiptInfo;
@ -112,9 +106,9 @@ export default createReactClass({
rrInfo.top = avatarNode.offsetTop; rrInfo.top = avatarNode.offsetTop;
rrInfo.left = avatarNode.offsetLeft; rrInfo.left = avatarNode.offsetLeft;
rrInfo.parent = avatarNode.offsetParent; rrInfo.parent = avatarNode.offsetParent;
}, }
componentDidMount: function() { componentDidMount() {
if (!this.state.suppressDisplay) { if (!this.state.suppressDisplay) {
// we've already done our display - nothing more to do. // we've already done our display - nothing more to do.
return; return;
@ -172,10 +166,9 @@ export default createReactClass({
startStyles: startStyles, startStyles: startStyles,
enterTransitionOpts: enterTransitionOpts, enterTransitionOpts: enterTransitionOpts,
}); });
}, }
render() {
render: function() {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
if (this.state.suppressDisplay) { if (this.state.suppressDisplay) {
return <div ref={this._avatar} />; return <div ref={this._avatar} />;
@ -222,5 +215,5 @@ export default createReactClass({
/> />
</Velociraptor> </Velociraptor>
); );
}, }
}); }

View file

@ -19,35 +19,32 @@ import dis from '../../../dispatcher/dispatcher';
import React from 'react'; import React from 'react';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames'; import classNames from 'classnames';
import {roomShape} from './RoomDetailRow'; import {roomShape} from './RoomDetailRow';
export default createReactClass({ export default class RoomDetailList extends React.Component {
displayName: 'RoomDetailList', static propTypes = {
propTypes: {
rooms: PropTypes.arrayOf(roomShape), rooms: PropTypes.arrayOf(roomShape),
className: PropTypes.string, className: PropTypes.string,
}, };
getRows: function() { getRows() {
if (!this.props.rooms) return []; if (!this.props.rooms) return [];
const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow'); const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow');
return this.props.rooms.map((room, index) => { return this.props.rooms.map((room, index) => {
return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />; return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />;
}); });
}, }
onDetailsClick: function(ev, room) { onDetailsClick = (ev, room) => {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: room.roomId, room_id: room.roomId,
room_alias: room.canonicalAlias || (room.aliases || [])[0], room_alias: room.canonicalAlias || (room.aliases || [])[0],
}); });
}, };
render() { render() {
const rows = this.getRows(); const rows = this.getRows();
@ -64,5 +61,5 @@ export default createReactClass({
return <div className={classNames("mx_RoomDetailList", this.props.className)}> return <div className={classNames("mx_RoomDetailList", this.props.className)}>
{ rooms } { rooms }
</div>; </div>;
}, }
}); }

View file

@ -20,7 +20,6 @@ import { _t } from '../../../languageHandler';
import { linkifyElement } from '../../../HtmlUtils'; import { linkifyElement } from '../../../HtmlUtils';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
export function getDisplayAliasForRoom(room) { export function getDisplayAliasForRoom(room) {
@ -40,47 +39,48 @@ export const roomShape = PropTypes.shape({
guestCanJoin: PropTypes.bool, guestCanJoin: PropTypes.bool,
}); });
export default createReactClass({ export default class RoomDetailRow extends React.Component {
propTypes: { static propTypes = {
room: roomShape, room: roomShape,
// passes ev, room as args // passes ev, room as args
onClick: PropTypes.func, onClick: PropTypes.func,
onMouseDown: PropTypes.func, onMouseDown: PropTypes.func,
}, };
_linkifyTopic: function() { constructor(props) {
super(props);
this._topic = createRef();
}
componentDidMount() {
this._linkifyTopic();
}
componentDidUpdate() {
this._linkifyTopic();
}
_linkifyTopic() {
if (this._topic.current) { if (this._topic.current) {
linkifyElement(this._topic.current); linkifyElement(this._topic.current);
} }
}, }
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs onClick = (ev) => {
UNSAFE_componentWillMount: function() {
this._topic = createRef();
},
componentDidMount: function() {
this._linkifyTopic();
},
componentDidUpdate: function() {
this._linkifyTopic();
},
onClick: function(ev) {
ev.preventDefault(); ev.preventDefault();
if (this.props.onClick) { if (this.props.onClick) {
this.props.onClick(ev, this.props.room); this.props.onClick(ev, this.props.room);
} }
}, };
onTopicClick: function(ev) { onTopicClick = (ev) => {
// When clicking a link in the topic, prevent the event being propagated // When clicking a link in the topic, prevent the event being propagated
// to `onClick`. // to `onClick`.
ev.stopPropagation(); ev.stopPropagation();
}, };
render: function() { render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const room = this.props.room; const room = this.props.room;
@ -118,5 +118,5 @@ export default createReactClass({
{ room.numJoinedMembers } { room.numJoinedMembers }
</td> </td>
</tr>; </tr>;
}, }
}); }

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames'; import classNames from 'classnames';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -35,10 +34,8 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import {DefaultTagID} from "../../../stores/room-list/models"; import {DefaultTagID} from "../../../stores/room-list/models";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
export default createReactClass({ export default class RoomHeader extends React.Component {
displayName: 'RoomHeader', static propTypes = {
propTypes: {
room: PropTypes.object, room: PropTypes.object,
oobData: PropTypes.object, oobData: PropTypes.object,
inRoom: PropTypes.bool, inRoom: PropTypes.bool,
@ -48,22 +45,21 @@ export default createReactClass({
onLeaveClick: PropTypes.func, onLeaveClick: PropTypes.func,
onCancelClick: PropTypes.func, onCancelClick: PropTypes.func,
e2eStatus: PropTypes.string, e2eStatus: PropTypes.string,
}, };
getDefaultProps: function() { static defaultProps = {
return { editing: false,
editing: false, inRoom: false,
inRoom: false, onCancelClick: null,
onCancelClick: null, };
};
}, constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._topic = createRef(); this._topic = createRef();
}, }
componentDidMount: function() { componentDidMount() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
cli.on("RoomState.events", this._onRoomStateEvents); cli.on("RoomState.events", this._onRoomStateEvents);
cli.on("Room.accountData", this._onRoomAccountData); cli.on("Room.accountData", this._onRoomAccountData);
@ -74,15 +70,15 @@ export default createReactClass({
if (this.props.room) { if (this.props.room) {
this.props.room.on("Room.name", this._onRoomNameChange); this.props.room.on("Room.name", this._onRoomNameChange);
} }
}, }
componentDidUpdate: function() { componentDidUpdate() {
if (this._topic.current) { if (this._topic.current) {
linkifyElement(this._topic.current); linkifyElement(this._topic.current);
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
if (this.props.room) { if (this.props.room) {
this.props.room.removeListener("Room.name", this._onRoomNameChange); this.props.room.removeListener("Room.name", this._onRoomNameChange);
} }
@ -91,41 +87,41 @@ export default createReactClass({
cli.removeListener("RoomState.events", this._onRoomStateEvents); cli.removeListener("RoomState.events", this._onRoomStateEvents);
cli.removeListener("Room.accountData", this._onRoomAccountData); cli.removeListener("Room.accountData", this._onRoomAccountData);
} }
}, }
_onRoomStateEvents: function(event, state) { _onRoomStateEvents = (event, state) => {
if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
return; return;
} }
// redisplay the room name, topic, etc. // redisplay the room name, topic, etc.
this._rateLimitedUpdate(); this._rateLimitedUpdate();
}, };
_onRoomAccountData: function(event, room) { _onRoomAccountData = (event, room) => {
if (!this.props.room || room.roomId !== this.props.room.roomId) return; if (!this.props.room || room.roomId !== this.props.room.roomId) return;
if (event.getType() !== "im.vector.room.read_pins") return; if (event.getType() !== "im.vector.room.read_pins") return;
this._rateLimitedUpdate(); this._rateLimitedUpdate();
}, };
_rateLimitedUpdate: new RateLimitedFunc(function() { _rateLimitedUpdate = new RateLimitedFunc(function() {
/* eslint-disable babel/no-invalid-this */ /* eslint-disable babel/no-invalid-this */
this.forceUpdate(); this.forceUpdate();
}, 500), }, 500);
_onRoomNameChange: function(room) { _onRoomNameChange = (room) => {
this.forceUpdate(); this.forceUpdate();
}, };
onShareRoomClick: function(ev) { onShareRoomClick = (ev) => {
const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room dialog', '', ShareDialog, { Modal.createTrackedDialog('share room dialog', '', ShareDialog, {
target: this.props.room, target: this.props.room,
}); });
}, };
_hasUnreadPins: function() { _hasUnreadPins() {
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
if (!currentPinEvent) return false; if (!currentPinEvent) return false;
if (currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0) { if (currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0) {
@ -142,16 +138,16 @@ export default createReactClass({
// There's pins, and we haven't read any of them // There's pins, and we haven't read any of them
return true; return true;
}, }
_hasPins: function() { _hasPins() {
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
if (!currentPinEvent) return false; if (!currentPinEvent) return false;
return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0); return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0);
}, }
render: function() { render() {
let searchStatus = null; let searchStatus = null;
let cancelButton = null; let cancelButton = null;
let settingsButton = null; let settingsButton = null;
@ -301,5 +297,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
@ -46,10 +45,8 @@ const MessageCase = Object.freeze({
OtherError: "OtherError", OtherError: "OtherError",
}); });
export default createReactClass({ export default class RoomPreviewBar extends React.Component {
displayName: 'RoomPreviewBar', static propTypes = {
propTypes: {
onJoinClick: PropTypes.func, onJoinClick: PropTypes.func,
onRejectClick: PropTypes.func, onRejectClick: PropTypes.func,
onRejectAndIgnoreClick: PropTypes.func, onRejectAndIgnoreClick: PropTypes.func,
@ -86,36 +83,32 @@ export default createReactClass({
// If given, this will be how the room is referred to (eg. // If given, this will be how the room is referred to (eg.
// in error messages). // in error messages).
roomAlias: PropTypes.string, roomAlias: PropTypes.string,
}, };
getDefaultProps: function() { static defaultProps = {
return { onJoinClick() {},
onJoinClick: function() {}, };
};
},
getInitialState: function() { state = {
return { busy: false,
busy: false, };
};
},
componentDidMount: function() { componentDidMount() {
this._checkInvitedEmail(); this._checkInvitedEmail();
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this._onCommunityUpdate); CommunityPrototypeStore.instance.on(UPDATE_EVENT, this._onCommunityUpdate);
}, }
componentDidUpdate: function(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
if (this.props.invitedEmail !== prevProps.invitedEmail || this.props.inviterName !== prevProps.inviterName) { if (this.props.invitedEmail !== prevProps.invitedEmail || this.props.inviterName !== prevProps.inviterName) {
this._checkInvitedEmail(); this._checkInvitedEmail();
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this._onCommunityUpdate); CommunityPrototypeStore.instance.off(UPDATE_EVENT, this._onCommunityUpdate);
}, }
_checkInvitedEmail: async function() { async _checkInvitedEmail() {
// If this is an invite and we've been told what email address was // If this is an invite and we've been told what email address was
// invited, fetch the user's account emails and discovery bindings so we // invited, fetch the user's account emails and discovery bindings so we
// can check them against the email that was invited. // can check them against the email that was invited.
@ -148,14 +141,14 @@ export default createReactClass({
} }
this.setState({busy: false}); this.setState({busy: false});
} }
}, }
_onCommunityUpdate: function (roomId) { _onCommunityUpdate = (roomId) => {
if (this.props.room && this.props.room.roomId !== roomId) { if (this.props.room && this.props.room.roomId !== roomId) {
return; return;
} }
this.forceUpdate(); // we have nothing to update this.forceUpdate(); // we have nothing to update
}, };
_getMessageCase() { _getMessageCase() {
const isGuest = MatrixClientPeg.get().isGuest(); const isGuest = MatrixClientPeg.get().isGuest();
@ -207,7 +200,7 @@ export default createReactClass({
} else { } else {
return MessageCase.ViewingRoom; return MessageCase.ViewingRoom;
} }
}, }
_getKickOrBanInfo() { _getKickOrBanInfo() {
const myMember = this._getMyMember(); const myMember = this._getMyMember();
@ -221,9 +214,9 @@ export default createReactClass({
kickerMember.name : myMember.events.member.getSender(); kickerMember.name : myMember.events.member.getSender();
const reason = myMember.events.member.getContent().reason; const reason = myMember.events.member.getContent().reason;
return {memberName, reason}; return {memberName, reason};
}, }
_joinRule: function() { _joinRule() {
const room = this.props.room; const room = this.props.room;
if (room) { if (room) {
const joinRules = room.currentState.getStateEvents('m.room.join_rules', ''); const joinRules = room.currentState.getStateEvents('m.room.join_rules', '');
@ -231,14 +224,14 @@ export default createReactClass({
return joinRules.getContent().join_rule; return joinRules.getContent().join_rule;
} }
} }
}, }
_communityProfile: function() { _communityProfile() {
if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId); if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId);
return {displayName: null, avatarMxc: null}; return {displayName: null, avatarMxc: null};
}, }
_roomName: function(atStart = false) { _roomName(atStart = false) {
let name = this.props.room ? this.props.room.name : this.props.roomAlias; let name = this.props.room ? this.props.room.name : this.props.roomAlias;
const profile = this._communityProfile(); const profile = this._communityProfile();
if (profile.displayName) name = profile.displayName; if (profile.displayName) name = profile.displayName;
@ -249,16 +242,16 @@ export default createReactClass({
} else { } else {
return _t("this room"); return _t("this room");
} }
}, }
_getMyMember() { _getMyMember() {
return ( return (
this.props.room && this.props.room &&
this.props.room.getMember(MatrixClientPeg.get().getUserId()) this.props.room.getMember(MatrixClientPeg.get().getUserId())
); );
}, }
_getInviteMember: function() { _getInviteMember() {
const {room} = this.props; const {room} = this.props;
if (!room) { if (!room) {
return; return;
@ -270,7 +263,7 @@ export default createReactClass({
} }
const inviterUserId = inviteEvent.events.member.getSender(); const inviterUserId = inviteEvent.events.member.getSender();
return room.currentState.getMember(inviterUserId); return room.currentState.getMember(inviterUserId);
}, }
_isDMInvite() { _isDMInvite() {
const myMember = this._getMyMember(); const myMember = this._getMyMember();
@ -280,7 +273,7 @@ export default createReactClass({
const memberEvent = myMember.events.member; const memberEvent = myMember.events.member;
const memberContent = memberEvent.getContent(); const memberContent = memberEvent.getContent();
return memberContent.membership === "invite" && memberContent.is_direct; return memberContent.membership === "invite" && memberContent.is_direct;
}, }
_makeScreenAfterLogin() { _makeScreenAfterLogin() {
return { return {
@ -293,17 +286,17 @@ export default createReactClass({
inviter_name: this.props.oobData ? this.props.oobData.inviterName : null, inviter_name: this.props.oobData ? this.props.oobData.inviterName : null,
} }
}; };
}, }
onLoginClick: function() { onLoginClick = () => {
dis.dispatch({ action: 'start_login', screenAfterLogin: this._makeScreenAfterLogin() }); dis.dispatch({ action: 'start_login', screenAfterLogin: this._makeScreenAfterLogin() });
}, };
onRegisterClick: function() { onRegisterClick = () => {
dis.dispatch({ action: 'start_registration', screenAfterLogin: this._makeScreenAfterLogin() }); dis.dispatch({ action: 'start_registration', screenAfterLogin: this._makeScreenAfterLogin() });
}, };
render: function() { render() {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
const Spinner = sdk.getComponent('elements.Spinner'); const Spinner = sdk.getComponent('elements.Spinner');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -597,5 +590,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -16,29 +16,26 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {MatrixClientPeg} from "../../../MatrixClientPeg";
export default createReactClass({ export default class RoomUpgradeWarningBar extends React.Component {
displayName: 'RoomUpgradeWarningBar', static propTypes = {
propTypes: {
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,
recommendation: PropTypes.object.isRequired, recommendation: PropTypes.object.isRequired,
}, };
componentDidMount: function() { componentDidMount() {
const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", ""); const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", "");
this.setState({upgraded: tombstone && tombstone.getContent().replacement_room}); this.setState({upgraded: tombstone && tombstone.getContent().replacement_room});
MatrixClientPeg.get().on("RoomState.events", this._onStateEvents); MatrixClientPeg.get().on("RoomState.events", this._onStateEvents);
}, }
_onStateEvents: function(event, state) { _onStateEvents = (event, state) => {
if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
return; return;
} }
@ -47,14 +44,14 @@ export default createReactClass({
const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", ""); const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", "");
this.setState({upgraded: tombstone && tombstone.getContent().replacement_room}); this.setState({upgraded: tombstone && tombstone.getContent().replacement_room});
}, };
onUpgradeClick: function() { onUpgradeClick = () => {
const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog'); const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: this.props.room}); Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: this.props.room});
}, };
render: function() { render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let doUpgradeWarnings = ( let doUpgradeWarnings = (
@ -117,5 +114,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -15,35 +15,31 @@ limitations under the License.
*/ */
import React, {createRef} from 'react'; import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import classNames from "classnames"; import classNames from "classnames";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {Key} from "../../../Keyboard"; import {Key} from "../../../Keyboard";
export default createReactClass({ export default class SearchBar extends React.Component {
displayName: 'SearchBar', constructor(props) {
super(props);
getInitialState: function() {
return ({
scope: 'Room',
});
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._search_term = createRef(); this._search_term = createRef();
},
onThisRoomClick: function() { this.state = {
scope: 'Room',
};
}
onThisRoomClick = () => {
this.setState({ scope: 'Room' }, () => this._searchIfQuery()); this.setState({ scope: 'Room' }, () => this._searchIfQuery());
}, };
onAllRoomsClick: function() { onAllRoomsClick = () => {
this.setState({ scope: 'All' }, () => this._searchIfQuery()); this.setState({ scope: 'All' }, () => this._searchIfQuery());
}, };
onSearchChange: function(e) { onSearchChange = (e) => {
switch (e.key) { switch (e.key) {
case Key.ENTER: case Key.ENTER:
this.onSearch(); this.onSearch();
@ -52,19 +48,19 @@ export default createReactClass({
this.props.onCancelClick(); this.props.onCancelClick();
break; break;
} }
}, };
_searchIfQuery: function() { _searchIfQuery() {
if (this._search_term.current.value) { if (this._search_term.current.value) {
this.onSearch(); this.onSearch();
} }
}, }
onSearch: function() { onSearch = () => {
this.props.onSearch(this._search_term.current.value, this.state.scope); this.props.onSearch(this._search_term.current.value, this.state.scope);
}, };
render: function() { render() {
const searchButtonClasses = classNames("mx_SearchBar_searchButton", { const searchButtonClasses = classNames("mx_SearchBar_searchButton", {
mx_SearchBar_searching: this.props.searchInProgress, mx_SearchBar_searching: this.props.searchInProgress,
}); });
@ -92,5 +88,5 @@ export default createReactClass({
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick} /> <AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick} />
</div> </div>
); );
}, }
}); }

View file

@ -17,14 +17,11 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import {haveTileForEvent} from "./EventTile"; import {haveTileForEvent} from "./EventTile";
export default createReactClass({ export default class SearchResultTile extends React.Component {
displayName: 'SearchResult', static propTypes = {
propTypes: {
// a matrix-js-sdk SearchResult containing the details of this result // a matrix-js-sdk SearchResult containing the details of this result
searchResult: PropTypes.object.isRequired, searchResult: PropTypes.object.isRequired,
@ -35,9 +32,9 @@ export default createReactClass({
resultLink: PropTypes.string, resultLink: PropTypes.string,
onHeightChanged: PropTypes.func, onHeightChanged: PropTypes.func,
}, };
render: function() { render() {
const DateSeparator = sdk.getComponent('messages.DateSeparator'); const DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventTile = sdk.getComponent('rooms.EventTile'); const EventTile = sdk.getComponent('rooms.EventTile');
const result = this.props.searchResult; const result = this.props.searchResult;
@ -66,5 +63,5 @@ export default createReactClass({
<li data-scroll-tokens={eventId+"+"+j}> <li data-scroll-tokens={eventId+"+"+j}>
{ ret } { ret }
</li>); </li>);
}, }
}); }

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -37,18 +36,16 @@ export function CancelButton(props) {
* A stripped-down room header used for things like the user settings * A stripped-down room header used for things like the user settings
* and room directory. * and room directory.
*/ */
export default createReactClass({ export default class SimpleRoomHeader extends React.Component {
displayName: 'SimpleRoomHeader', static propTypes = {
propTypes: {
title: PropTypes.string, title: PropTypes.string,
onCancelClick: PropTypes.func, onCancelClick: PropTypes.func,
// `src` to a TintableSvg. Optional. // `src` to a TintableSvg. Optional.
icon: PropTypes.string, icon: PropTypes.string,
}, };
render: function() { render() {
let cancelButton; let cancelButton;
let icon; let icon;
if (this.props.onCancelClick) { if (this.props.onCancelClick) {
@ -73,5 +70,5 @@ export default createReactClass({
</div> </div>
</div> </div>
); );
}, }
}); }

View file

@ -18,19 +18,16 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
export default createReactClass({ export default class TopUnreadMessagesBar extends React.Component {
displayName: 'TopUnreadMessagesBar', static propTypes = {
propTypes: {
onScrollUpClick: PropTypes.func, onScrollUpClick: PropTypes.func,
onCloseClick: PropTypes.func, onCloseClick: PropTypes.func,
}, };
render: function() { render() {
return ( return (
<div className="mx_TopUnreadMessagesBar"> <div className="mx_TopUnreadMessagesBar">
<AccessibleButton className="mx_TopUnreadMessagesBar_scrollUp" <AccessibleButton className="mx_TopUnreadMessagesBar_scrollUp"
@ -43,5 +40,5 @@ export default createReactClass({
</AccessibleButton> </AccessibleButton>
</div> </div>
); );
}, }
}); }

View file

@ -17,16 +17,13 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as WhoIsTyping from '../../../WhoIsTyping'; import * as WhoIsTyping from '../../../WhoIsTyping';
import Timer from '../../../utils/Timer'; import Timer from '../../../utils/Timer';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import MemberAvatar from '../avatars/MemberAvatar'; import MemberAvatar from '../avatars/MemberAvatar';
export default createReactClass({ export default class WhoIsTypingTile extends React.Component {
displayName: 'WhoIsTypingTile', static propTypes = {
propTypes: {
// the room this statusbar is representing. // the room this statusbar is representing.
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,
onShown: PropTypes.func, onShown: PropTypes.func,
@ -34,32 +31,28 @@ export default createReactClass({
// Number of names to display in typing indication. E.g. set to 3, will // Number of names to display in typing indication. E.g. set to 3, will
// result in "X, Y, Z and 100 others are typing." // result in "X, Y, Z and 100 others are typing."
whoIsTypingLimit: PropTypes.number, whoIsTypingLimit: PropTypes.number,
}, };
getDefaultProps: function() { static defaultProps = {
return { whoIsTypingLimit: 3,
whoIsTypingLimit: 3, };
};
},
getInitialState: function() { state = {
return { usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), // a map with userid => Timer to delay
// a map with userid => Timer to delay // hiding the "x is typing" message for a
// hiding the "x is typing" message for a // user so hiding it can coincide
// user so hiding it can coincide // with the sent message by the other side
// with the sent message by the other side // resulting in less timeline jumpiness
// resulting in less timeline jumpiness delayedStopTypingTimers: {},
delayedStopTypingTimers: {}, };
};
},
componentDidMount: function() { componentDidMount() {
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping); MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
}, }
componentDidUpdate: function(_, prevState) { componentDidUpdate(_, prevState) {
const wasVisible = this._isVisible(prevState); const wasVisible = this._isVisible(prevState);
const isVisible = this._isVisible(this.state); const isVisible = this._isVisible(this.state);
if (this.props.onShown && !wasVisible && isVisible) { if (this.props.onShown && !wasVisible && isVisible) {
@ -67,9 +60,9 @@ export default createReactClass({
} else if (this.props.onHidden && wasVisible && !isVisible) { } else if (this.props.onHidden && wasVisible && !isVisible) {
this.props.onHidden(); this.props.onHidden();
} }
}, }
componentWillUnmount: function() { componentWillUnmount() {
// we may have entirely lost our client as we're logging out before clicking login on the guest bar... // we may have entirely lost our client as we're logging out before clicking login on the guest bar...
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (client) { if (client) {
@ -77,17 +70,17 @@ export default createReactClass({
client.removeListener("Room.timeline", this.onRoomTimeline); client.removeListener("Room.timeline", this.onRoomTimeline);
} }
Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort()); Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort());
}, }
_isVisible: function(state) { _isVisible(state) {
return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0; return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0;
}, }
isVisible: function() { isVisible = () => {
return this._isVisible(this.state); return this._isVisible(this.state);
}, };
onRoomTimeline: function(event, room) { onRoomTimeline = (event, room) => {
if (room && room.roomId === this.props.room.roomId) { if (room && room.roomId === this.props.room.roomId) {
const userId = event.getSender(); const userId = event.getSender();
// remove user from usersTyping // remove user from usersTyping
@ -96,15 +89,15 @@ export default createReactClass({
// abort timer if any // abort timer if any
this._abortUserTimer(userId); this._abortUserTimer(userId);
} }
}, };
onRoomMemberTyping: function(ev, member) { onRoomMemberTyping = (ev, member) => {
const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room); const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room);
this.setState({ this.setState({
delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping), delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping),
usersTyping, usersTyping,
}); });
}, };
_updateDelayedStopTypingTimers(usersTyping) { _updateDelayedStopTypingTimers(usersTyping) {
const usersThatStoppedTyping = this.state.usersTyping.filter((a) => { const usersThatStoppedTyping = this.state.usersTyping.filter((a) => {
@ -142,26 +135,26 @@ export default createReactClass({
}, delayedStopTypingTimers); }, delayedStopTypingTimers);
return delayedStopTypingTimers; return delayedStopTypingTimers;
}, }
_abortUserTimer: function(userId) { _abortUserTimer(userId) {
const timer = this.state.delayedStopTypingTimers[userId]; const timer = this.state.delayedStopTypingTimers[userId];
if (timer) { if (timer) {
timer.abort(); timer.abort();
this._removeUserTimer(userId); this._removeUserTimer(userId);
} }
}, }
_removeUserTimer: function(userId) { _removeUserTimer(userId) {
const timer = this.state.delayedStopTypingTimers[userId]; const timer = this.state.delayedStopTypingTimers[userId];
if (timer) { if (timer) {
const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers); const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers);
delete delayedStopTypingTimers[userId]; delete delayedStopTypingTimers[userId];
this.setState({delayedStopTypingTimers}); this.setState({delayedStopTypingTimers});
} }
}, }
_renderTypingIndicatorAvatars: function(users, limit) { _renderTypingIndicatorAvatars(users, limit) {
let othersCount = 0; let othersCount = 0;
if (users.length > limit) { if (users.length > limit) {
othersCount = users.length - limit + 1; othersCount = users.length - limit + 1;
@ -190,9 +183,9 @@ export default createReactClass({
} }
return avatars; return avatars;
}, }
render: function() { render() {
let usersTyping = this.state.usersTyping; let usersTyping = this.state.usersTyping;
const stoppedUsersOnTimer = Object.keys(this.state.delayedStopTypingTimers) const stoppedUsersOnTimer = Object.keys(this.state.delayedStopTypingTimers)
.map((userId) => this.props.room.getMember(userId)); .map((userId) => this.props.room.getMember(userId));
@ -222,5 +215,5 @@ export default createReactClass({
</div> </div>
</li> </li>
); );
}, }
}); }

Some files were not shown because too many files have changed in this diff Show more