From 72498df28f3e20cc968cea692a9fd49ebb64973e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 29 Aug 2020 12:14:16 +0100 Subject: [PATCH 01/10] Remove create-react-class --- package.json | 1 - src/AsyncWrapper.js | 36 +- .../views/dialogs/ExportE2eKeysDialog.js | 48 ++- .../views/dialogs/ImportE2eKeysDialog.js | 54 ++- src/components/structures/FilePanel.js | 50 ++- src/components/structures/GroupView.js | 265 ++++++------- src/components/structures/InteractiveAuth.js | 79 ++-- src/components/structures/MyGroups.js | 37 +- .../structures/NotificationPanel.js | 16 +- src/components/structures/RoomDirectory.js | 108 +++--- src/components/structures/RoomStatusBar.js | 79 ++-- src/components/structures/RoomView.js | 360 +++++++++--------- src/components/structures/ScrollPanel.js | 151 ++++---- src/components/structures/SearchBox.js | 74 ++-- src/components/structures/TagPanel.js | 47 +-- src/components/structures/TimelinePanel.js | 243 ++++++------ src/components/structures/UploadBar.js | 26 +- src/components/structures/ViewSource.js | 15 +- .../structures/auth/ForgotPassword.js | 108 +++--- src/components/structures/auth/Login.js | 110 +++--- .../structures/auth/PostRegistration.js | 31 +- .../structures/auth/Registration.js | 88 +++-- src/components/views/auth/AuthFooter.js | 11 +- src/components/views/auth/AuthHeader.js | 15 +- src/components/views/auth/CaptchaForm.js | 54 ++- .../views/auth/CustomServerDialog.js | 11 +- .../auth/InteractiveAuthEntryComponents.js | 227 +++++------ src/components/views/auth/RegistrationForm.js | 147 ++++--- .../views/context_menus/MessageContextMenu.js | 111 +++--- .../views/dialogs/AddressPickerDialog.js | 152 ++++---- .../views/dialogs/AskInviteAnywayDialog.js | 25 +- src/components/views/dialogs/BaseDialog.js | 40 +- .../views/dialogs/ConfirmRedactDialog.js | 11 +- .../views/dialogs/ConfirmUserActionDialog.js | 37 +- .../views/dialogs/CreateGroupDialog.js | 56 ++- .../views/dialogs/CreateRoomDialog.js | 82 ++-- src/components/views/dialogs/ErrorDialog.js | 28 +- src/components/views/dialogs/InfoDialog.js | 30 +- .../views/dialogs/InteractiveAuthDialog.js | 45 +-- .../views/dialogs/QuestionDialog.js | 42 +- .../views/dialogs/RoomUpgradeDialog.js | 37 +- .../dialogs/SessionRestoreErrorDialog.js | 27 +- .../views/dialogs/SetEmailDialog.js | 44 +-- src/components/views/dialogs/SetMxIdDialog.js | 63 ++- .../views/dialogs/SetPasswordDialog.js | 34 +- .../views/dialogs/TextInputDialog.js | 67 ++-- src/components/views/elements/ActionButton.js | 45 +-- .../views/elements/AddressSelector.js | 72 ++-- src/components/views/elements/AddressTile.js | 27 +- .../views/elements/DialogButtons.js | 29 +- src/components/views/elements/EditableText.js | 141 ++++--- .../views/elements/InlineSpinner.js | 11 +- .../views/elements/MemberEventListSummary.js | 61 ++- .../views/elements/PersistentApp.js | 37 +- src/components/views/elements/Pill.js | 92 ++--- .../views/elements/PowerSelector.js | 62 ++- src/components/views/elements/TagTile.js | 59 ++- src/components/views/elements/TintableSvg.js | 39 +- .../views/elements/TooltipButton.js | 27 +- .../views/elements/TruncatedList.js | 41 +- .../views/groups/GroupInviteTile.js | 57 ++- .../views/groups/GroupMemberList.js | 71 ++-- .../views/groups/GroupMemberTile.js | 27 +- .../views/groups/GroupPublicityToggle.js | 39 +- src/components/views/groups/GroupRoomInfo.js | 67 ++-- src/components/views/groups/GroupRoomList.js | 61 ++- src/components/views/groups/GroupRoomTile.js | 24 +- src/components/views/groups/GroupTile.js | 47 +-- .../views/groups/GroupUserSettings.js | 27 +- src/components/views/messages/MFileBody.js | 64 ++-- src/components/views/messages/MVideoBody.js | 53 ++- src/components/views/messages/MessageEvent.js | 30 +- .../views/messages/RoomAvatarEvent.js | 19 +- src/components/views/messages/RoomCreate.js | 19 +- .../views/messages/SenderProfile.js | 38 +- src/components/views/messages/TextualBody.js | 112 +++--- src/components/views/messages/TextualEvent.js | 15 +- .../views/room_settings/UrlPreviewSettings.js | 19 +- src/components/views/rooms/AppsDrawer.js | 65 ++-- src/components/views/rooms/AuxPanel.js | 57 +-- src/components/views/rooms/EntityTile.js | 55 ++- src/components/views/rooms/EventTile.js | 126 +++--- src/components/views/rooms/ForwardMessage.js | 27 +- .../views/rooms/LinkPreviewWidget.js | 42 +- src/components/views/rooms/MemberList.js | 147 ++++--- src/components/views/rooms/MemberTile.js | 73 ++-- src/components/views/rooms/PinnedEventTile.js | 30 +- .../views/rooms/PinnedEventsPanel.js | 46 +-- src/components/views/rooms/PresenceLabel.js | 33 +- .../views/rooms/ReadReceiptMarker.js | 57 ++- src/components/views/rooms/RoomDetailList.js | 21 +- src/components/views/rooms/RoomDetailRow.js | 46 +-- src/components/views/rooms/RoomHeader.js | 78 ++-- src/components/views/rooms/RoomPreviewBar.js | 85 ++--- .../views/rooms/RoomUpgradeWarningBar.js | 27 +- src/components/views/rooms/SearchBar.js | 46 +-- .../views/rooms/SearchResultTile.js | 15 +- .../views/rooms/SimpleRoomHeader.js | 15 +- .../views/rooms/TopUnreadMessagesBar.js | 15 +- src/components/views/rooms/WhoIsTypingTile.js | 91 ++--- src/components/views/settings/ChangeAvatar.js | 84 ++-- .../views/settings/ChangeDisplayName.js | 19 +- .../views/settings/ChangePassword.js | 121 +++--- .../views/settings/Notifications.js | 153 ++++---- src/components/views/voip/VideoFeed.js | 30 +- src/components/views/voip/VideoView.js | 50 ++- .../structures/MessagePanel-test.js | 22 +- test/components/stub-component.js | 20 +- 108 files changed, 3082 insertions(+), 3568 deletions(-) diff --git a/package.json b/package.json index ce559b12753..00f6dc8a964 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "classnames": "^2.2.6", "commonmark": "^0.29.1", "counterpart": "^0.18.6", - "create-react-class": "^15.6.3", "diff-dom": "^4.1.6", "diff-match-patch": "^1.0.5", "emojibase-data": "^5.0.1", diff --git a/src/AsyncWrapper.js b/src/AsyncWrapper.js index 94de5df2145..359828b3126 100644 --- a/src/AsyncWrapper.js +++ b/src/AsyncWrapper.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import createReactClass from 'create-react-class'; +import React from "react"; import * as sdk from './index'; import PropTypes from 'prop-types'; import { _t } from './languageHandler'; @@ -24,21 +24,19 @@ import { _t } from './languageHandler'; * Wrap an asynchronous loader function with a react component which shows a * spinner until the real component loads. */ -export default createReactClass({ - propTypes: { +export default class AsyncWrapper extends React.Component { + static propTypes = { /** A promise which resolves with the real component */ prom: PropTypes.object.isRequired, - }, + }; - getInitialState: function() { - return { - component: null, - error: null, - }; - }, + state = { + component: null, + error: null, + }; - componentDidMount: function() { + componentDidMount() { this._unmounted = false; // XXX: temporary logging to try to diagnose // https://github.com/vector-im/element-web/issues/3148 @@ -56,17 +54,17 @@ export default createReactClass({ console.warn('AsyncWrapper promise failed', e); this.setState({error: e}); }); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this._unmounted = true; - }, + } - _onWrapperCancelClick: function() { + _onWrapperCancelClick = () => { this.props.onFinished(false); - }, + }; - render: function() { + render() { if (this.state.component) { const Component = this.state.component; return ; @@ -87,6 +85,6 @@ export default createReactClass({ const Spinner = sdk.getComponent("elements.Spinner"); return ; } - }, -}); + } +} diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/ExportE2eKeysDialog.js index a92578a5474..406ffd87495 100644 --- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ExportE2eKeysDialog.js @@ -17,7 +17,6 @@ limitations under the License. import FileSaver from 'file-saver'; import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; import { MatrixClient } from 'matrix-js-sdk'; @@ -27,34 +26,31 @@ import * as sdk from '../../../index'; const PHASE_EDIT = 1; const PHASE_EXPORTING = 2; -export default createReactClass({ - displayName: 'ExportE2eKeysDialog', - - propTypes: { +export default class ExportE2eKeysDialog extends React.Component { + static propTypes = { matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, onFinished: PropTypes.func.isRequired, - }, + }; - getInitialState: function() { - return { - phase: PHASE_EDIT, - errStr: null, - }; - }, + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this._unmounted = false; this._passphrase1 = createRef(); this._passphrase2 = createRef(); - }, - componentWillUnmount: function() { + this.state = { + phase: PHASE_EDIT, + errStr: null, + }; + } + + componentWillUnmount() { this._unmounted = true; - }, + } - _onPassphraseFormSubmit: function(ev) { + _onPassphraseFormSubmit = (ev) => { ev.preventDefault(); const passphrase = this._passphrase1.current.value; @@ -69,9 +65,9 @@ export default createReactClass({ this._startExport(passphrase); return false; - }, + }; - _startExport: function(passphrase) { + _startExport(passphrase) { // extra Promise.resolve() to turn synchronous exceptions into // asynchronous ones. Promise.resolve().then(() => { @@ -102,15 +98,15 @@ export default createReactClass({ errStr: null, phase: PHASE_EXPORTING, }); - }, + } - _onCancelClick: function(ev) { + _onCancelClick = (ev) => { ev.preventDefault(); this.props.onFinished(false); return false; - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const disableForm = (this.state.phase === PHASE_EXPORTING); @@ -184,5 +180,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/async-components/views/dialogs/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/ImportE2eKeysDialog.js index 6b9d2c7e45f..c2d17f681d1 100644 --- a/src/async-components/views/dialogs/ImportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ImportE2eKeysDialog.js @@ -16,7 +16,6 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { MatrixClient } from 'matrix-js-sdk'; import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; @@ -38,48 +37,45 @@ function readFileAsArrayBuffer(file) { const PHASE_EDIT = 1; const PHASE_IMPORTING = 2; -export default createReactClass({ - displayName: 'ImportE2eKeysDialog', - - propTypes: { +export default class ImportE2eKeysDialog extends React.Component { + static propTypes = { matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, onFinished: PropTypes.func.isRequired, - }, + }; - getInitialState: function() { - return { - enableSubmit: false, - phase: PHASE_EDIT, - errStr: null, - }; - }, + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this._unmounted = false; this._file = createRef(); this._passphrase = createRef(); - }, - componentWillUnmount: function() { + this.state = { + enableSubmit: false, + phase: PHASE_EDIT, + errStr: null, + }; + } + + componentWillUnmount() { this._unmounted = true; - }, + } - _onFormChange: function(ev) { + _onFormChange = (ev) => { const files = this._file.current.files || []; this.setState({ enableSubmit: (this._passphrase.current.value !== "" && files.length > 0), }); - }, + }; - _onFormSubmit: function(ev) { + _onFormSubmit = (ev) => { ev.preventDefault(); this._startImport(this._file.current.files[0], this._passphrase.current.value); return false; - }, + }; - _startImport: function(file, passphrase) { + _startImport(file, passphrase) { this.setState({ errStr: null, phase: PHASE_IMPORTING, @@ -105,15 +101,15 @@ export default createReactClass({ phase: PHASE_EDIT, }); }); - }, + } - _onCancelClick: function(ev) { + _onCancelClick = (ev) => { ev.preventDefault(); this.props.onFinished(false); return false; - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const disableForm = (this.state.phase !== PHASE_EDIT); @@ -188,5 +184,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index d873dd40947..5a2d858c9d6 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import {Filter} from 'matrix-js-sdk'; @@ -28,23 +27,20 @@ import { _t } from '../../languageHandler'; /* * Component which shows the filtered file using a TimelinePanel */ -const FilePanel = createReactClass({ - displayName: 'FilePanel', +class FilePanel extends React.Component { + static propTypes = { + roomId: PropTypes.string.isRequired, + }; + // This is used to track if a decrypted event was a live event and should be // added to the timeline. - decryptingEvents: new Set(), - - propTypes: { - roomId: PropTypes.string.isRequired, - }, + decryptingEvents = new Set(); - getInitialState: function() { - return { - timelineSet: null, - }; - }, + state = { + timelineSet: null, + }; - onRoomTimeline(ev, room, toStartOfTimeline, removed, data) { + onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => { if (room.roomId !== this.props.roomId) return; if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; @@ -53,9 +49,9 @@ const FilePanel = createReactClass({ } else { this.addEncryptedLiveEvent(ev); } - }, + }; - onEventDecrypted(ev, err) { + onEventDecrypted = (ev, err) => { if (ev.getRoomId() !== this.props.roomId) return; const eventId = ev.getId(); @@ -63,7 +59,7 @@ const FilePanel = createReactClass({ if (err) return; this.addEncryptedLiveEvent(ev); - }, + }; addEncryptedLiveEvent(ev, toStartOfTimeline) { if (!this.state.timelineSet) return; @@ -77,7 +73,7 @@ const FilePanel = createReactClass({ if (!this.state.timelineSet.eventIdToTimeline(ev.getId())) { this.state.timelineSet.addEventToTimeline(ev, timeline, false); } - }, + } async componentDidMount() { const client = MatrixClientPeg.get(); @@ -98,7 +94,7 @@ const FilePanel = createReactClass({ client.on('Room.timeline', this.onRoomTimeline); client.on('Event.decrypted', this.onEventDecrypted); } - }, + } componentWillUnmount() { const client = MatrixClientPeg.get(); @@ -110,7 +106,7 @@ const FilePanel = createReactClass({ client.removeListener('Room.timeline', this.onRoomTimeline); client.removeListener('Event.decrypted', this.onEventDecrypted); } - }, + } async fetchFileEventsServer(room) { const client = MatrixClientPeg.get(); @@ -134,9 +130,9 @@ const FilePanel = createReactClass({ const timelineSet = room.getOrCreateFilteredTimelineSet(filter); return timelineSet; - }, + } - onPaginationRequest(timelineWindow, direction, limit) { + onPaginationRequest = (timelineWindow, direction, limit) => { const client = MatrixClientPeg.get(); const eventIndex = EventIndexPeg.get(); const roomId = this.props.roomId; @@ -152,7 +148,7 @@ const FilePanel = createReactClass({ } else { return timelineWindow.paginate(direction, limit); } - }, + }; async updateTimelineSet(roomId: string) { const client = MatrixClientPeg.get(); @@ -188,9 +184,9 @@ const FilePanel = createReactClass({ } else { console.error("Failed to add filtered timelineSet for FilePanel as no room!"); } - }, + } - render: function() { + render() { if (MatrixClientPeg.get().isGuest()) { return
@@ -239,7 +235,7 @@ const FilePanel = createReactClass({
); } - }, -}); + } +} export default FilePanel; diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 2e2fa251696..83f70eb72a5 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import * as sdk from '../../index'; @@ -70,10 +69,8 @@ const UserSummaryType = PropTypes.shape({ }).isRequired, }); -const CategoryRoomList = createReactClass({ - displayName: 'CategoryRoomList', - - props: { +class CategoryRoomList extends React.Component { + static propTypes = { rooms: PropTypes.arrayOf(RoomSummaryType).isRequired, category: PropTypes.shape({ profile: PropTypes.shape({ @@ -84,9 +81,9 @@ const CategoryRoomList = createReactClass({ // Whether the list should be editable editing: PropTypes.bool.isRequired, - }, + }; - onAddRoomsToSummaryClicked: function(ev) { + onAddRoomsToSummaryClicked = (ev) => { ev.preventDefault(); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, { @@ -122,9 +119,9 @@ const CategoryRoomList = createReactClass({ }); }, }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); - }, + }; - render: function() { + render() { const TintableSvg = sdk.getComponent("elements.TintableSvg"); const addButton = this.props.editing ? (; - }, -}); + } +} -const FeaturedRoom = createReactClass({ - displayName: 'FeaturedRoom', - - props: { +class FeaturedRoom extends React.Component { + static propTypes = { summaryInfo: RoomSummaryType.isRequired, editing: PropTypes.bool.isRequired, groupId: PropTypes.string.isRequired, - }, + }; - onClick: function(e) { + onClick = (e) => { e.preventDefault(); e.stopPropagation(); @@ -176,9 +171,9 @@ const FeaturedRoom = createReactClass({ room_alias: this.props.summaryInfo.profile.canonical_alias, room_id: this.props.summaryInfo.room_id, }); - }, + }; - onDeleteClicked: function(e) { + onDeleteClicked = (e) => { e.preventDefault(); e.stopPropagation(); GroupStore.removeRoomFromGroupSummary( @@ -201,9 +196,9 @@ const FeaturedRoom = createReactClass({ description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}), }); }); - }, + }; - render: function() { + render() { const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); const roomName = this.props.summaryInfo.profile.name || @@ -243,13 +238,11 @@ const FeaturedRoom = createReactClass({
{ roomNameNode }
{ deleteButton }
; - }, -}); + } +} -const RoleUserList = createReactClass({ - displayName: 'RoleUserList', - - props: { +class RoleUserList extends React.Component { + static propTypes = { users: PropTypes.arrayOf(UserSummaryType).isRequired, role: PropTypes.shape({ profile: PropTypes.shape({ @@ -260,9 +253,9 @@ const RoleUserList = createReactClass({ // Whether the list should be editable editing: PropTypes.bool.isRequired, - }, + }; - onAddUsersClicked: function(ev) { + onAddUsersClicked = (ev) => { ev.preventDefault(); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, { @@ -298,9 +291,9 @@ const RoleUserList = createReactClass({ }); }, }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); - }, + }; - render: function() { + render() { const TintableSvg = sdk.getComponent("elements.TintableSvg"); const addButton = this.props.editing ? ( @@ -325,19 +318,17 @@ const RoleUserList = createReactClass({ { userNodes } { addButton }
; - }, -}); - -const FeaturedUser = createReactClass({ - displayName: 'FeaturedUser', + } +} - props: { +class FeaturedUser extends React.Component { + static propTypes = { summaryInfo: UserSummaryType.isRequired, editing: PropTypes.bool.isRequired, groupId: PropTypes.string.isRequired, - }, + }; - onClick: function(e) { + onClick = (e) => { e.preventDefault(); e.stopPropagation(); @@ -345,9 +336,9 @@ const FeaturedUser = createReactClass({ action: 'view_start_chat_or_reuse', user_id: this.props.summaryInfo.user_id, }); - }, + }; - onDeleteClicked: function(e) { + onDeleteClicked = (e) => { e.preventDefault(); e.stopPropagation(); GroupStore.removeUserFromGroupSummary( @@ -368,9 +359,9 @@ const FeaturedUser = createReactClass({ description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}), }); }); - }, + }; - render: function() { + render() { const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id; @@ -394,41 +385,37 @@ const FeaturedUser = createReactClass({
{ userNameNode }
{ deleteButton } ; - }, -}); + } +} const GROUP_JOINPOLICY_OPEN = "open"; const GROUP_JOINPOLICY_INVITE = "invite"; -export default createReactClass({ - displayName: 'GroupView', - - propTypes: { +export default class GroupView extends React.Component { + static propTypes = { groupId: PropTypes.string.isRequired, // Whether this is the first time the group admin is viewing the group groupIsNew: PropTypes.bool, - }, - - getInitialState: function() { - return { - summary: null, - isGroupPublicised: null, - isUserPrivileged: null, - groupRooms: null, - groupRoomsLoading: null, - error: null, - editing: false, - saving: false, - uploadingAvatar: false, - avatarChanged: false, - membershipBusy: false, - publicityBusy: false, - inviterProfile: null, - showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, - }; - }, - - componentDidMount: function() { + }; + + state = { + summary: null, + isGroupPublicised: null, + isUserPrivileged: null, + groupRooms: null, + groupRoomsLoading: null, + error: null, + editing: false, + saving: false, + uploadingAvatar: false, + avatarChanged: false, + membershipBusy: false, + publicityBusy: false, + inviterProfile: null, + showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, + }; + + componentDidMount() { this._unmounted = false; this._matrixClient = MatrixClientPeg.get(); this._matrixClient.on("Group.myMembership", this._onGroupMyMembership); @@ -437,9 +424,9 @@ export default createReactClass({ this._dispatcherRef = dis.register(this._onAction); this._rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this._onRightPanelStoreUpdate); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this._unmounted = true; this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership); dis.unregister(this._dispatcherRef); @@ -448,10 +435,11 @@ export default createReactClass({ if (this._rightPanelStoreToken) { this._rightPanelStoreToken.remove(); } - }, + } // 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) { this.setState({ summary: null, @@ -460,24 +448,24 @@ export default createReactClass({ this._initGroupStore(newProps.groupId); }); } - }, + } - _onRightPanelStoreUpdate: function() { + _onRightPanelStoreUpdate = () => { this.setState({ showRightPanel: RightPanelStore.getSharedInstance().isOpenForGroup, }); - }, + }; - _onGroupMyMembership: function(group) { + _onGroupMyMembership = (group) => { if (this._unmounted || group.groupId !== this.props.groupId) return; if (group.myMembership === 'leave') { // Leave settings - the user might have clicked the "Leave" button this._closeSettings(); } this.setState({membershipBusy: false}); - }, + }; - _initGroupStore: function(groupId, firstInit) { + _initGroupStore(groupId, firstInit) { const group = this._matrixClient.getGroup(groupId); if (group && group.inviter && group.inviter.userId) { this._fetchInviterProfile(group.inviter.userId); @@ -506,9 +494,9 @@ export default createReactClass({ }); } }); - }, + } - onGroupStoreUpdated(firstInit) { + onGroupStoreUpdated = (firstInit) => { if (this._unmounted) return; const summary = GroupStore.getSummary(this.props.groupId); if (summary.profile) { @@ -533,7 +521,7 @@ export default createReactClass({ if (this.props.groupIsNew && firstInit) { this._onEditClick(); } - }, + }; _fetchInviterProfile(userId) { this.setState({ @@ -555,9 +543,9 @@ export default createReactClass({ inviterProfileBusy: false, }); }); - }, + } - _onEditClick: function() { + _onEditClick = () => { this.setState({ editing: true, profileForm: Object.assign({}, this.state.summary.profile), @@ -568,20 +556,20 @@ export default createReactClass({ GROUP_JOINPOLICY_INVITE, }, }); - }, + }; - _onShareClick: function() { + _onShareClick = () => { const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); Modal.createTrackedDialog('share community dialog', '', ShareDialog, { target: this._matrixClient.getGroup(this.props.groupId) || new Group(this.props.groupId), }); - }, + }; - _onCancelClick: function() { + _onCancelClick = () => { this._closeSettings(); - }, + }; - _onAction(payload) { + _onAction = (payload) => { switch (payload.action) { // NOTE: close_settings is an app-wide dispatch; as it is dispatched from MatrixChat case 'close_settings': @@ -593,34 +581,34 @@ export default createReactClass({ default: break; } - }, + }; - _closeSettings() { + _closeSettings = () => { dis.dispatch({action: 'close_settings'}); - }, + }; - _onNameChange: function(value) { + _onNameChange = (value) => { const newProfileForm = Object.assign(this.state.profileForm, { name: value }); this.setState({ profileForm: newProfileForm, }); - }, + }; - _onShortDescChange: function(value) { + _onShortDescChange = (value) => { const newProfileForm = Object.assign(this.state.profileForm, { short_description: value }); this.setState({ profileForm: newProfileForm, }); - }, + }; - _onLongDescChange: function(e) { + _onLongDescChange = (e) => { const newProfileForm = Object.assign(this.state.profileForm, { long_description: e.target.value }); this.setState({ profileForm: newProfileForm, }); - }, + }; - _onAvatarSelected: function(ev) { + _onAvatarSelected = ev => { const file = ev.target.files[0]; if (!file) return; @@ -644,15 +632,15 @@ export default createReactClass({ description: _t('Failed to upload image'), }); }); - }, + }; - _onJoinableChange: function(ev) { + _onJoinableChange = ev => { this.setState({ joinableForm: { policyType: ev.target.value }, }); - }, + }; - _onSaveClick: function() { + _onSaveClick = () => { this.setState({saving: true}); const savePromise = this.state.isUserPrivileged ? this._saveGroup() : Promise.resolve(); savePromise.then((result) => { @@ -683,16 +671,16 @@ export default createReactClass({ avatarChanged: false, }); }); - }, + }; - _saveGroup: async function() { + async _saveGroup() { await this._matrixClient.setGroupProfile(this.props.groupId, this.state.profileForm); await this._matrixClient.setGroupJoinPolicy(this.props.groupId, { type: this.state.joinableForm.policyType, }); - }, + } - _onAcceptInviteClick: async function() { + _onAcceptInviteClick = async () => { this.setState({membershipBusy: true}); // 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"), }); }); - }, + }; - _onRejectInviteClick: async function() { + _onRejectInviteClick = async () => { this.setState({membershipBusy: true}); // 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"), }); }); - }, + }; - _onJoinClick: async function() { + _onJoinClick = async () => { if (this._matrixClient.isGuest()) { dis.dispatch({action: 'require_registration', screen_after: {screen: `group/${this.props.groupId}`}}); return; @@ -752,9 +740,9 @@ export default createReactClass({ description: _t("Unable to join community"), }); }); - }, + }; - _leaveGroupWarnings: function() { + _leaveGroupWarnings() { const warnings = []; if (this.state.isUserPrivileged) { @@ -768,10 +756,9 @@ export default createReactClass({ } return warnings; - }, - + } - _onLeaveClick: function() { + _onLeaveClick = () => { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const warnings = this._leaveGroupWarnings(); @@ -806,13 +793,13 @@ export default createReactClass({ }); }, }); - }, + }; - _onAddRoomsClick: function() { + _onAddRoomsClick = () => { showGroupAddRoomDialog(this.props.groupId); - }, + }; - _getGroupSection: function() { + _getGroupSection() { const groupSettingsSectionClasses = classnames({ "mx_GroupView_group": this.state.editing, "mx_GroupView_group_disabled": this.state.editing && !this.state.isUserPrivileged, @@ -856,9 +843,9 @@ export default createReactClass({ { this._getLongDescriptionNode() } { this._getRoomsNode() } ; - }, + } - _getRoomsNode: function() { + _getRoomsNode() { const RoomDetailList = sdk.getComponent('rooms.RoomDetailList'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const TintableSvg = sdk.getComponent('elements.TintableSvg'); @@ -902,9 +889,9 @@ export default createReactClass({ className={roomDetailListClassName} /> } ; - }, + } - _getFeaturedRoomsNode: function() { + _getFeaturedRoomsNode() { const summary = this.state.summary; const defaultCategoryRooms = []; @@ -943,9 +930,9 @@ export default createReactClass({ { defaultCategoryNode } { categoryRoomNodes } ; - }, + } - _getFeaturedUsersNode: function() { + _getFeaturedUsersNode() { const summary = this.state.summary; const noRoleUsers = []; @@ -984,9 +971,9 @@ export default createReactClass({ { noRoleNode } { roleUserNodes } ; - }, + } - _getMembershipSection: function() { + _getMembershipSection() { const Spinner = sdk.getComponent("elements.Spinner"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); @@ -1100,9 +1087,9 @@ export default createReactClass({ ; - }, + } - _getJoinableNode: function() { + _getJoinableNode() { const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); return this.state.editing ?

@@ -1136,9 +1123,9 @@ export default createReactClass({

: null; - }, + } - _getLongDescriptionNode: function() { + _getLongDescriptionNode() { const summary = this.state.summary; let description = null; if (summary.profile && summary.profile.long_description) { @@ -1175,9 +1162,9 @@ export default createReactClass({
{ description }
; - }, + } - render: function() { + render() { const GroupAvatar = sdk.getComponent("avatars.GroupAvatar"); const Spinner = sdk.getComponent("elements.Spinner"); @@ -1366,5 +1353,5 @@ export default createReactClass({ console.error("Invalid state for GroupView"); return
; } - }, -}); + } +} diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js index fa7860ccef4..4c186bce300 100644 --- a/src/components/structures/InteractiveAuth.js +++ b/src/components/structures/InteractiveAuth.js @@ -17,7 +17,6 @@ limitations under the License. import {InteractiveAuth} from "matrix-js-sdk"; import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents'; @@ -26,10 +25,8 @@ import * as sdk from '../../index'; export const ERROR_USER_CANCELLED = new Error("User cancelled auth session"); -export default createReactClass({ - displayName: 'InteractiveAuth', - - propTypes: { +export default class InteractiveAuthComponent extends React.Component { + static propTypes = { // matrix client to use for UI auth requests matrixClient: PropTypes.object.isRequired, @@ -86,20 +83,19 @@ export default createReactClass({ // continueText and continueKind are passed straight through to the AuthEntryComponent. continueText: PropTypes.string, continueKind: PropTypes.string, - }, + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this.state = { authStage: null, busy: false, errorText: null, stageErrorText: null, submitButtonEnabled: false, }; - }, - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this._unmounted = false; this._authLogic = new InteractiveAuth({ authData: this.props.authData, @@ -141,17 +137,17 @@ export default createReactClass({ } this._stageComponent = createRef(); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this._unmounted = true; if (this._intervalId !== null) { clearInterval(this._intervalId); } - }, + } - _requestEmailToken: async function(...args) { + _requestEmailToken = async (...args) => { this.setState({ busy: true, }); @@ -162,15 +158,15 @@ export default createReactClass({ busy: false, }); } - }, + }; - tryContinue: function() { + tryContinue = () => { if (this._stageComponent.current && this._stageComponent.current.tryContinue) { this._stageComponent.current.tryContinue(); } - }, + }; - _authStateUpdated: function(stageType, stageState) { + _authStateUpdated = (stageType, stageState) => { const oldStage = this.state.authStage; this.setState({ busy: false, @@ -180,16 +176,16 @@ export default createReactClass({ }, () => { if (oldStage != stageType) this._setFocus(); }); - }, + }; - _requestCallback: function(auth) { + _requestCallback = (auth) => { // This wrapper just exists because the js-sdk passes a second // 'busy' param for backwards compat. This throws the tests off // so discard it here. return this.props.makeRequest(auth); - }, + }; - _onBusyChanged: function(busy) { + _onBusyChanged = (busy) => { // if we've started doing stuff, reset the error messages if (busy) { this.setState({ @@ -204,29 +200,29 @@ export default createReactClass({ // there's a new screen to show the user. This is implemented by setting // `busy: false` in `_authStateUpdated`. // See also https://github.com/vector-im/element-web/issues/12546 - }, + }; - _setFocus: function() { + _setFocus() { if (this._stageComponent.current && this._stageComponent.current.focus) { this._stageComponent.current.focus(); } - }, + } - _submitAuthDict: function(authData) { + _submitAuthDict = authData => { this._authLogic.submitAuthDict(authData); - }, + }; - _onPhaseChange: function(newPhase) { + _onPhaseChange = newPhase => { if (this.props.onStagePhaseChange) { this.props.onStagePhaseChange(this.state.authStage, newPhase || 0); } - }, + }; - _onStageCancel: function() { + _onStageCancel = () => { this.props.onAuthFinished(false, ERROR_USER_CANCELLED); - }, + }; - _renderCurrentStage: function() { + _renderCurrentStage() { const stage = this.state.authStage; if (!stage) { if (this.state.busy) { @@ -260,16 +256,17 @@ export default createReactClass({ onCancel={this._onStageCancel} /> ); - }, + } - _onAuthStageFailed: function(e) { + _onAuthStageFailed = e => { this.props.onAuthFinished(false, e); - }, - _setEmailSid: function(sid) { + }; + + _setEmailSid = sid => { this._authLogic.setEmailSid(sid); - }, + }; - render: function() { + render() { let error = null; if (this.state.errorText) { error = ( @@ -287,5 +284,5 @@ export default createReactClass({
); - }, -}); + } +} diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js index 7043c7f38a3..e0551eecdbc 100644 --- a/src/components/structures/MyGroups.js +++ b/src/components/structures/MyGroups.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import * as sdk from '../../index'; import { _t } from '../../languageHandler'; import SdkConfig from '../../SdkConfig'; @@ -26,29 +25,23 @@ import AccessibleButton from '../views/elements/AccessibleButton'; import MatrixClientContext from "../../contexts/MatrixClientContext"; import AutoHideScrollbar from "./AutoHideScrollbar"; -export default createReactClass({ - displayName: 'MyGroups', +export default class MyGroups extends React.Component { + static contextType = MatrixClientContext; - getInitialState: function() { - return { - groups: null, - error: null, - }; - }, + state = { + groups: null, + error: null, + }; - statics: { - contextType: MatrixClientContext, - }, - - componentDidMount: function() { + componentDidMount() { this._fetch(); - }, + } - _onCreateGroupClick: function() { + _onCreateGroupClick = () => { dis.dispatch({action: 'view_create_group'}); - }, + }; - _fetch: function() { + _fetch() { this.context.getJoinedGroups().then((result) => { this.setState({groups: result.groups, error: null}); }, (err) => { @@ -59,9 +52,9 @@ export default createReactClass({ } this.setState({groups: null, error: err}); }); - }, + } - render: function() { + render() { const brand = SdkConfig.get().brand; const Loader = sdk.getComponent("elements.Spinner"); const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader'); @@ -149,5 +142,5 @@ export default createReactClass({ { content } ; - }, -}); + } +} diff --git a/src/components/structures/NotificationPanel.js b/src/components/structures/NotificationPanel.js index c1f78cffdab..6ae7f91142a 100644 --- a/src/components/structures/NotificationPanel.js +++ b/src/components/structures/NotificationPanel.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import { _t } from '../../languageHandler'; import {MatrixClientPeg} from "../../MatrixClientPeg"; import * as sdk from "../../index"; @@ -25,13 +24,8 @@ import * as sdk from "../../index"; /* * Component which shows the global notification list using a TimelinePanel */ -const NotificationPanel = createReactClass({ - displayName: 'NotificationPanel', - - propTypes: { - }, - - render: function() { +class NotificationPanel extends React.Component { + render() { // wrap a TimelinePanel with the jump-to-event bits turned off. const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); const Loader = sdk.getComponent("elements.Spinner"); @@ -45,7 +39,7 @@ const NotificationPanel = createReactClass({ if (timelineSet) { return (
- ); } - }, -}); + } +} export default NotificationPanel; diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 11d3508ee53..16ab8edbeda 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import {MatrixClientPeg} from "../../MatrixClientPeg"; import * as sdk from "../../index"; import dis from "../../dispatcher/dispatcher"; @@ -42,16 +41,16 @@ function track(action) { Analytics.trackEvent('RoomDirectory', action); } -export default createReactClass({ - displayName: 'RoomDirectory', - - propTypes: { +export default class RoomDirectory extends React.Component { + static propTypes = { onFinished: PropTypes.func.isRequired, - }, + }; + + constructor(props) { + super(props); - getInitialState: function() { const selectedCommunityId = TagOrderStore.getSelectedTags()[0]; - return { + this.state = { publicRooms: [], loading: true, protocolsLoading: true, @@ -64,10 +63,7 @@ export default createReactClass({ : null, communityName: null, }; - }, - // TODO: [REACT-WARNING] Move this to constructor - UNSAFE_componentWillMount: function() { this._unmounted = false; this.nextBatch = null; this.filterTimeout = null; @@ -115,16 +111,16 @@ export default createReactClass({ } this.refreshRoomList(); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { if (this.filterTimeout) { clearTimeout(this.filterTimeout); } this._unmounted = true; - }, + } - refreshRoomList: function() { + refreshRoomList = () => { if (this.state.selectedCommunityId) { this.setState({ publicRooms: GroupStore.getGroupRooms(this.state.selectedCommunityId).map(r => { @@ -158,9 +154,9 @@ export default createReactClass({ loading: true, }); this.getMoreRooms(); - }, + }; - getMoreRooms: function() { + getMoreRooms() { if (this.state.selectedCommunityId) return Promise.resolve(); // no more rooms if (!MatrixClientPeg.get()) return Promise.resolve(); @@ -233,7 +229,7 @@ export default createReactClass({ ), }); }); - }, + } /** * A limited interface for removing rooms from the directory. @@ -242,7 +238,7 @@ export default createReactClass({ * HS admins to do this through the RoomSettings interface, but * this needs SPEC-417. */ - removeFromDirectory: function(room) { + removeFromDirectory(room) { const alias = get_display_alias_for_room(room); const name = room.name || alias || _t('Unnamed room'); @@ -284,18 +280,18 @@ export default createReactClass({ }); }, }); - }, + } - onRoomClicked: function(room, ev) { + onRoomClicked = (room, ev) => { if (ev.shiftKey && !this.state.selectedCommunityId) { ev.preventDefault(); this.removeFromDirectory(room); } else { this.showRoom(room); } - }, + }; - onOptionChange: function(server, instanceId) { + onOptionChange = (server, instanceId) => { // clear next batch so we don't try to load more rooms this.nextBatch = null; this.setState({ @@ -313,15 +309,15 @@ export default createReactClass({ // find the five gitter ones, at which point we do not want // to render all those rooms when switching back to 'all networks'. // Easiest to just blow away the state & re-fetch. - }, + }; - onFillRequest: function(backwards) { + onFillRequest = (backwards) => { if (backwards || !this.nextBatch) return Promise.resolve(false); return this.getMoreRooms(); - }, + }; - onFilterChange: function(alias) { + onFilterChange = (alias) => { this.setState({ filterString: alias || null, }); @@ -337,9 +333,9 @@ export default createReactClass({ this.filterTimeout = null; this.refreshRoomList(); }, 700); - }, + }; - onFilterClear: function() { + onFilterClear = () => { // update immediately this.setState({ filterString: null, @@ -348,9 +344,9 @@ export default createReactClass({ if (this.filterTimeout) { clearTimeout(this.filterTimeout); } - }, + }; - onJoinFromSearchClick: function(alias) { + onJoinFromSearchClick = (alias) => { // If we don't have a particular instance id selected, just show that rooms alias if (!this.state.instanceId || this.state.instanceId === ALL_ROOMS) { // If the user specified an alias without a domain, add on whichever server is selected @@ -391,9 +387,9 @@ export default createReactClass({ }); }); } - }, + }; - onPreviewClick: function(ev, room) { + onPreviewClick = (ev, room) => { this.props.onFinished(); dis.dispatch({ action: 'view_room', @@ -401,9 +397,9 @@ export default createReactClass({ should_peek: true, }); ev.stopPropagation(); - }, + }; - onViewClick: function(ev, room) { + onViewClick = (ev, room) => { this.props.onFinished(); dis.dispatch({ action: 'view_room', @@ -411,26 +407,26 @@ export default createReactClass({ should_peek: false, }); ev.stopPropagation(); - }, + }; - onJoinClick: function(ev, room) { + onJoinClick = (ev, room) => { this.showRoom(room, null, true); ev.stopPropagation(); - }, + }; - onCreateRoomClick: function(room) { + onCreateRoomClick = room => { this.props.onFinished(); dis.dispatch({ action: 'view_create_room', public: true, }); - }, + }; - showRoomAlias: function(alias, autoJoin=false) { + showRoomAlias(alias, autoJoin=false) { this.showRoom(null, alias, autoJoin); - }, + } - showRoom: function(room, room_alias, autoJoin=false) { + showRoom(room, room_alias, autoJoin=false) { this.props.onFinished(); const payload = { action: 'view_room', @@ -474,7 +470,7 @@ export default createReactClass({ payload.room_id = room.room_id; } dis.dispatch(payload); - }, + } getRow(room) { const client = MatrixClientPeg.get(); @@ -540,22 +536,22 @@ export default createReactClass({ {joinOrViewButton} ); - }, + } - collectScrollPanel: function(element) { + collectScrollPanel = (element) => { this.scrollPanel = element; - }, + }; - _stringLooksLikeId: function(s, field_type) { + _stringLooksLikeId(s, field_type) { let pat = /^#[^\s]+:[^\s]/; if (field_type && field_type.regexp) { pat = new RegExp(field_type.regexp); } return pat.test(s); - }, + } - _getFieldsForThirdPartyLocation: function(userInput, protocol, instance) { + _getFieldsForThirdPartyLocation(userInput, protocol, instance) { // make an object with the fields specified by that protocol. We // require that the values of all but the last field come from the // instance. The last is the user input. @@ -569,20 +565,20 @@ export default createReactClass({ } fields[requiredFields[requiredFields.length - 1]] = userInput; return fields; - }, + } /** * called by the parent component when PageUp/Down/etc is pressed. * * We pass it down to the scroll panel. */ - handleScrollKey: function(ev) { + handleScrollKey = ev => { if (this.scrollPanel) { this.scrollPanel.handleScrollKey(ev); } - }, + }; - render: function() { + render() { const Loader = sdk.getComponent("elements.Spinner"); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); @@ -712,8 +708,8 @@ export default createReactClass({
); - }, -}); + } +} // Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom // but works with the objects we get from the public room list diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 3171dbccbe4..cdaa0bb7f9a 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import Matrix from 'matrix-js-sdk'; import { _t, _td } from '../../languageHandler'; @@ -39,10 +38,8 @@ function getUnsentMessages(room) { }); } -export default createReactClass({ - displayName: 'RoomStatusBar', - - propTypes: { +export default class RoomStatusBar extends React.Component { + static propTypes = { // the room this statusbar is representing. room: PropTypes.object.isRequired, // This is true when the user is alone in the room, but has also sent a message. @@ -86,37 +83,35 @@ export default createReactClass({ // callback for when the status bar is displaying something and should // be visible onVisible: PropTypes.func, - }, + }; - getInitialState: function() { - return { - syncState: MatrixClientPeg.get().getSyncState(), - syncStateData: MatrixClientPeg.get().getSyncStateData(), - unsentMessages: getUnsentMessages(this.props.room), - }; - }, + state = { + syncState: MatrixClientPeg.get().getSyncState(), + syncStateData: MatrixClientPeg.get().getSyncStateData(), + unsentMessages: getUnsentMessages(this.props.room), + }; - componentDidMount: function() { + componentDidMount() { MatrixClientPeg.get().on("sync", this.onSyncStateChange); MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated); this._checkSize(); - }, + } - componentDidUpdate: function() { + componentDidUpdate() { this._checkSize(); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { // we may have entirely lost our client as we're logging out before clicking login on the guest bar... const client = MatrixClientPeg.get(); if (client) { client.removeListener("sync", this.onSyncStateChange); client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated); } - }, + } - onSyncStateChange: function(state, prevState, data) { + onSyncStateChange = (state, prevState, data) => { if (state === "SYNCING" && prevState === "SYNCING") { return; } @@ -124,39 +119,39 @@ export default createReactClass({ syncState: state, syncStateData: data, }); - }, + }; - _onResendAllClick: function() { + _onResendAllClick = () => { Resend.resendUnsentEvents(this.props.room); dis.fire(Action.FocusComposer); - }, + }; - _onCancelAllClick: function() { + _onCancelAllClick = () => { Resend.cancelUnsentEvents(this.props.room); dis.fire(Action.FocusComposer); - }, + }; - _onRoomLocalEchoUpdated: function(event, room, oldEventId, oldStatus) { + _onRoomLocalEchoUpdated = (event, room, oldEventId, oldStatus) => { if (room.roomId !== this.props.room.roomId) return; this.setState({ unsentMessages: getUnsentMessages(this.props.room), }); - }, + }; // Check whether current size is greater than 0, if yes call props.onVisible - _checkSize: function() { + _checkSize() { if (this._getSize()) { if (this.props.onVisible) this.props.onVisible(); } else { if (this.props.onHidden) this.props.onHidden(); } - }, + } // We don't need the actual height - just whether it is likely to have // changed - so we use '0' to indicate normal size, and other values to // indicate other sizes. - _getSize: function() { + _getSize() { if (this._shouldShowConnectionError() || this.props.hasActiveCall || this.props.sentMessageAndIsAlone @@ -166,10 +161,10 @@ export default createReactClass({ return STATUS_BAR_EXPANDED_LARGE; } return STATUS_BAR_HIDDEN; - }, + } // return suitable content for the image on the left of the status bar. - _getIndicator: function() { + _getIndicator() { if (this.props.hasActiveCall) { const TintableSvg = sdk.getComponent("elements.TintableSvg"); return ( @@ -182,9 +177,9 @@ export default createReactClass({ } return null; - }, + } - _shouldShowConnectionError: function() { + _shouldShowConnectionError() { // no conn bar trumps the "some not sent" msg since you can't resend without // a connection! // There's one situation in which we don't show this 'no connection' bar, and that's @@ -195,9 +190,9 @@ export default createReactClass({ this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED', ); return this.state.syncState === "ERROR" && !errorIsMauError; - }, + } - _getUnsentMessageContent: function() { + _getUnsentMessageContent() { const unsentMessages = this.state.unsentMessages; if (!unsentMessages.length) return null; @@ -272,10 +267,10 @@ export default createReactClass({ ; - }, + } // return suitable content for the main (text) part of the status bar. - _getContent: function() { + _getContent() { if (this._shouldShowConnectionError()) { return (
@@ -323,9 +318,9 @@ export default createReactClass({ } return null; - }, + } - render: function() { + render() { const content = this._getContent(); const indicator = this._getIndicator(); @@ -339,5 +334,5 @@ export default createReactClass({
); - }, -}); + } +} diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index a79e5b0aa8c..157f7266fa3 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -24,7 +24,6 @@ limitations under the License. import shouldHideEvent from '../../shouldHideEvent'; import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { _t } from '../../languageHandler'; @@ -68,9 +67,8 @@ if (DEBUG) { debuglog = console.log.bind(console); } -export default createReactClass({ - displayName: 'RoomView', - propTypes: { +export default class RoomView extends React.Component { + static propTypes = { ConferenceHandler: PropTypes.any, // Called with the credentials of a registered user (if they were a ROU that @@ -97,15 +95,15 @@ export default createReactClass({ // Servers the RoomView can use to try and assist joins viaServers: PropTypes.arrayOf(PropTypes.string), - }, + }; - statics: { - contextType: MatrixClientContext, - }, + static contextType = MatrixClientContext; + + constructor(props) { + super(props); - getInitialState: function() { const llMembers = this.context.hasLazyLoadMembersEnabled(); - return { + this.state = { room: null, roomId: null, roomLoading: true, @@ -171,10 +169,7 @@ export default createReactClass({ matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), }; - }, - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this.dispatcherRef = dis.register(this.onAction); this.context.on("Room", this.onRoom); this.context.on("Room.timeline", this.onRoomTimeline); @@ -201,15 +196,15 @@ export default createReactClass({ this._searchResultsPanel = createRef(); this._layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange); - }, + } - _onReadReceiptsChange: function() { + _onReadReceiptsChange = () => { this.setState({ showReadReceipts: SettingsStore.getValue("showReadReceipts", this.state.roomId), }); - }, + }; - _onRoomViewStoreUpdate: function(initial) { + _onRoomViewStoreUpdate = initial => { if (this.unmounted) { return; } @@ -303,7 +298,7 @@ export default createReactClass({ if (initial) { this._setupRoom(newState.room, newState.roomId, newState.joining, newState.shouldPeek); } - }, + }; _getRoomId() { // According to `_onRoomViewStoreUpdate`, `state.roomId` can be null @@ -312,9 +307,9 @@ export default createReactClass({ // the bare room ID. (We may want to update `state.roomId` after // resolving aliases, so we could always trust it.) return this.state.room ? this.state.room.roomId : this.state.roomId; - }, + } - _getPermalinkCreatorForRoom: function(room) { + _getPermalinkCreatorForRoom(room) { if (!this._permalinkCreators) this._permalinkCreators = {}; if (this._permalinkCreators[room.roomId]) return this._permalinkCreators[room.roomId]; @@ -327,22 +322,22 @@ export default createReactClass({ this._permalinkCreators[room.roomId].load(); } return this._permalinkCreators[room.roomId]; - }, + } - _stopAllPermalinkCreators: function() { + _stopAllPermalinkCreators() { if (!this._permalinkCreators) return; for (const roomId of Object.keys(this._permalinkCreators)) { this._permalinkCreators[roomId].stop(); } - }, + } - _onWidgetEchoStoreUpdate: function() { + _onWidgetEchoStoreUpdate = () => { this.setState({ showApps: this._shouldShowApps(this.state.room), }); - }, + }; - _setupRoom: function(room, roomId, joining, shouldPeek) { + _setupRoom(room, roomId, joining, shouldPeek) { // if this is an unknown room then we're in one of three states: // - This is a room we can peek into (search engine) (we can /peek) // - This is a room we can publicly join or were invited to. (we can /join) @@ -404,9 +399,9 @@ export default createReactClass({ this.setState({isPeeking: false}); } } - }, + } - _shouldShowApps: function(room) { + _shouldShowApps(room) { if (!BROWSER_SUPPORTS_SANDBOX) return false; // Check if user has previously chosen to hide the app drawer for this @@ -417,9 +412,9 @@ export default createReactClass({ // This is confusing, but it means to say that we default to the tray being // hidden unless the user clicked to open it. return hideWidgetDrawer === "false"; - }, + } - componentDidMount: function() { + componentDidMount() { const call = this._getCallForRoom(); const callState = call ? call.call_state : "ended"; this.setState({ @@ -435,14 +430,14 @@ export default createReactClass({ this.onResize(); document.addEventListener("keydown", this.onNativeKeyDown); - }, + } - shouldComponentUpdate: function(nextProps, nextState) { + shouldComponentUpdate(nextProps, nextState) { return (!ObjectUtils.shallowEqual(this.props, nextProps) || !ObjectUtils.shallowEqual(this.state, nextState)); - }, + } - componentDidUpdate: function() { + componentDidUpdate() { if (this._roomView.current) { const roomView = this._roomView.current; if (!roomView.ondrop) { @@ -464,9 +459,9 @@ export default createReactClass({ atEndOfLiveTimeline: this._messagePanel.isAtEndOfLiveTimeline(), }); } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { // set a boolean to say we've been unmounted, which any pending // promises can use to throw away their results. // @@ -543,21 +538,21 @@ export default createReactClass({ // Tinter.tint(); // reset colourscheme SettingsStore.unwatchSetting(this._layoutWatcherRef); - }, + } - onLayoutChange: function() { + onLayoutChange = () => { this.setState({ useIRCLayout: SettingsStore.getValue("useIRCLayout"), }); - }, + }; - _onRightPanelStoreUpdate: function() { + _onRightPanelStoreUpdate = () => { this.setState({ showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, }); - }, + }; - onPageUnload(event) { + onPageUnload = event => { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { return event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"); @@ -565,10 +560,10 @@ export default createReactClass({ return event.returnValue = _t("You seem to be in a call, are you sure you want to quit?"); } - }, + }; // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire - onNativeKeyDown: function(ev) { + onNativeKeyDown = ev => { let handled = false; const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev); @@ -592,9 +587,9 @@ export default createReactClass({ ev.stopPropagation(); ev.preventDefault(); } - }, + }; - onReactKeyDown: function(ev) { + onReactKeyDown = ev => { let handled = false; switch (ev.key) { @@ -613,7 +608,7 @@ export default createReactClass({ break; case Key.U.toUpperCase(): if (isOnlyCtrlOrCmdIgnoreShiftKeyEvent(ev) && ev.shiftKey) { - dis.dispatch({ action: "upload_file" }) + dis.dispatch({ action: "upload_file" }); handled = true; } break; @@ -623,9 +618,9 @@ export default createReactClass({ ev.stopPropagation(); ev.preventDefault(); } - }, + }; - onAction: function(payload) { + onAction = payload => { switch (payload.action) { case 'message_send_failed': case 'message_sent': @@ -709,9 +704,9 @@ export default createReactClass({ } break; } - }, + }; - onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) { + onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => { if (this.unmounted) return; // ignore events for other rooms @@ -747,51 +742,51 @@ export default createReactClass({ }); } } - }, + }; - onRoomName: function(room) { + onRoomName = room => { if (this.state.room && room.roomId == this.state.room.roomId) { this.forceUpdate(); } - }, + }; - onRoomRecoveryReminderDontAskAgain: function() { + onRoomRecoveryReminderDontAskAgain = () => { // Called when the option to not ask again is set: // force an update to hide the recovery reminder this.forceUpdate(); - }, + }; - onKeyBackupStatus() { + onKeyBackupStatus = () => { // Key backup status changes affect whether the in-room recovery // reminder is displayed. this.forceUpdate(); - }, + }; - canResetTimeline: function() { + canResetTimeline = () => { if (!this._messagePanel) { return true; } return this._messagePanel.canResetTimeline(); - }, + }; // called when state.room is first initialised (either at initial load, // after a successful peek, or after we join the room). - _onRoomLoaded: function(room) { + _onRoomLoaded = room => { this._calculatePeekRules(room); this._updatePreviewUrlVisibility(room); this._loadMembersIfJoined(room); this._calculateRecommendedVersion(room); this._updateE2EStatus(room); this._updatePermissions(room); - }, + }; - _calculateRecommendedVersion: async function(room) { + async _calculateRecommendedVersion(room) { this.setState({ upgradeRecommendation: await room.getRecommendedVersion(), }); - }, + } - _loadMembersIfJoined: async function(room) { + async _loadMembersIfJoined(room) { // lazy load members if enabled if (this.context.hasLazyLoadMembersEnabled()) { if (room && room.getMyMembership() === 'join') { @@ -808,9 +803,9 @@ export default createReactClass({ } } } - }, + } - _calculatePeekRules: function(room) { + _calculatePeekRules(room) { const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", ""); if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") { this.setState({ @@ -824,17 +819,17 @@ export default createReactClass({ canPeek: true, }); } - }, + } - _updatePreviewUrlVisibility: function({roomId}) { + _updatePreviewUrlVisibility({roomId}) { // URL Previews in E2EE rooms can be a privacy leak so use a different setting which is per-room explicit const key = this.context.isRoomEncrypted(roomId) ? 'urlPreviewsEnabled_e2ee' : 'urlPreviewsEnabled'; this.setState({ showUrlPreview: SettingsStore.getValue(key, roomId), }); - }, + } - onRoom: function(room) { + onRoom = room => { if (!room || room.roomId !== this.state.roomId) { return; } @@ -843,32 +838,32 @@ export default createReactClass({ }, () => { this._onRoomLoaded(room); }); - }, + }; - onDeviceVerificationChanged: function(userId, device) { + onDeviceVerificationChanged = (userId, device) => { const room = this.state.room; if (!room.currentState.getMember(userId)) { return; } this._updateE2EStatus(room); - }, + }; - onUserVerificationChanged: function(userId, _trustStatus) { + onUserVerificationChanged = (userId, _trustStatus) => { const room = this.state.room; if (!room || !room.currentState.getMember(userId)) { return; } this._updateE2EStatus(room); - }, + }; - onCrossSigningKeysChanged: function() { + onCrossSigningKeysChanged = () => { const room = this.state.room; if (room) { this._updateE2EStatus(room); } - }, + }; - _updateE2EStatus: async function(room) { + async _updateE2EStatus(room) { if (!this.context.isRoomEncrypted(room.roomId)) { return; } @@ -886,26 +881,26 @@ export default createReactClass({ this.setState({ e2eStatus: await shieldStatusForRoom(this.context, room), }); - }, + } - updateTint: function() { + updateTint() { const room = this.state.room; if (!room) return; console.log("Tinter.tint from updateTint"); const colorScheme = SettingsStore.getValue("roomColor", room.roomId); Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color); - }, + } - onAccountData: function(event) { + onAccountData = event => { const type = event.getType(); if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) { // non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls` this._updatePreviewUrlVisibility(this.state.room); } - }, + }; - onRoomAccountData: function(event, room) { + onRoomAccountData = (event, room) => { if (room.roomId == this.state.roomId) { const type = event.getType(); if (type === "org.matrix.room.color_scheme") { @@ -918,18 +913,18 @@ export default createReactClass({ this._updatePreviewUrlVisibility(room); } } - }, + }; - onRoomStateEvents: function(ev, state) { + onRoomStateEvents = (ev, state) => { // ignore if we don't have a room yet if (!this.state.room || this.state.room.roomId !== state.roomId) { return; } this._updatePermissions(this.state.room); - }, + }; - onRoomStateMember: function(ev, state, member) { + onRoomStateMember = (ev, state, member) => { // ignore if we don't have a room yet if (!this.state.room) { return; @@ -941,17 +936,17 @@ export default createReactClass({ } this._updateRoomMembers(member); - }, + }; - onMyMembership: function(room, membership, oldMembership) { + onMyMembership = (room, membership, oldMembership) => { if (room.roomId === this.state.roomId) { this.forceUpdate(); this._loadMembersIfJoined(room); this._updatePermissions(room); } - }, + }; - _updatePermissions: function(room) { + _updatePermissions(room) { if (room) { const me = this.context.getUserId(); const canReact = room.getMyMembership() === "join" && room.currentState.maySendEvent("m.reaction", me); @@ -959,11 +954,11 @@ export default createReactClass({ this.setState({canReact, canReply}); } - }, + } // rate limited because a power level change will emit an event for every // member in the room. - _updateRoomMembers: rate_limited_func(function(dueToMember) { + _updateRoomMembers = rate_limited_func((dueToMember) => { // a member state changed in this room // refresh the conf call notification state this._updateConfCallNotification(); @@ -978,9 +973,9 @@ export default createReactClass({ this._checkIfAlone(this.state.room, memberCountInfluence); this._updateE2EStatus(this.state.room); - }, 500), + }, 500); - _checkIfAlone: function(room, countInfluence) { + _checkIfAlone(room, countInfluence) { let warnedAboutLonelyRoom = false; if (localStorage) { warnedAboutLonelyRoom = localStorage.getItem('mx_user_alone_warned_' + this.state.room.roomId); @@ -993,9 +988,9 @@ export default createReactClass({ let joinedOrInvitedMemberCount = room.getJoinedMemberCount() + room.getInvitedMemberCount(); if (countInfluence) joinedOrInvitedMemberCount += countInfluence; this.setState({isAlone: joinedOrInvitedMemberCount === 1}); - }, + } - _updateConfCallNotification: function() { + _updateConfCallNotification() { const room = this.state.room; if (!room || !this.props.ConferenceHandler) { return; @@ -1017,7 +1012,7 @@ export default createReactClass({ confMember.membership === "join" ), }); - }, + } _updateDMState() { const room = this.state.room; @@ -1028,9 +1023,9 @@ export default createReactClass({ if (dmInviter) { Rooms.setDMRoom(room.roomId, dmInviter); } - }, + } - onSearchResultsFillRequest: function(backwards) { + onSearchResultsFillRequest = backwards => { if (!backwards) { return Promise.resolve(false); } @@ -1043,25 +1038,25 @@ export default createReactClass({ debuglog("no more search results"); return Promise.resolve(false); } - }, + }; - onInviteButtonClick: function() { + onInviteButtonClick = () => { // call AddressPickerDialog dis.dispatch({ action: 'view_invite', roomId: this.state.room.roomId, }); this.setState({isAlone: false}); // there's a good chance they'll invite someone - }, + }; - onStopAloneWarningClick: function() { + onStopAloneWarningClick = () => { if (localStorage) { localStorage.setItem('mx_user_alone_warned_' + this.state.room.roomId, true); } this.setState({isAlone: false}); - }, + }; - onJoinButtonClicked: function(ev) { + onJoinButtonClicked = ev => { // If the user is a ROU, allow them to transition to a PWLU if (this.context && this.context.isGuest()) { // Join this room once the user has registered and logged in @@ -1120,10 +1115,9 @@ export default createReactClass({ return Promise.resolve(); }); } + }; - }, - - onMessageListScroll: function(ev) { + onMessageListScroll = ev => { if (this._messagePanel.isAtEndOfLiveTimeline()) { this.setState({ numUnreadMessages: 0, @@ -1135,9 +1129,9 @@ export default createReactClass({ }); } this._updateTopUnreadMessagesBar(); - }, + }; - onDragOver: function(ev) { + onDragOver = ev => { ev.stopPropagation(); ev.preventDefault(); @@ -1154,9 +1148,9 @@ export default createReactClass({ ev.dataTransfer.dropEffect = 'copy'; } } - }, + }; - onDrop: function(ev) { + onDrop = ev => { ev.stopPropagation(); ev.preventDefault(); ContentMessages.sharedInstance().sendContentListToRoom( @@ -1164,15 +1158,15 @@ export default createReactClass({ ); this.setState({ draggingFile: false }); dis.fire(Action.FocusComposer); - }, + }; - onDragLeaveOrEnd: function(ev) { + onDragLeaveOrEnd = ev => { ev.stopPropagation(); ev.preventDefault(); this.setState({ draggingFile: false }); - }, + }; - injectSticker: function(url, info, text) { + injectSticker(url, info, text) { if (this.context.isGuest()) { dis.dispatch({action: 'require_registration'}); return; @@ -1185,9 +1179,9 @@ export default createReactClass({ return; } }); - }, + } - onSearch: function(term, scope) { + onSearch = (term, scope) => { this.setState({ searchTerm: term, searchScope: scope, @@ -1213,9 +1207,9 @@ export default createReactClass({ debuglog("sending search request"); const searchPromise = eventSearch(term, roomId); this._handleSearchResult(searchPromise); - }, + }; - _handleSearchResult: function(searchPromise) { + _handleSearchResult(searchPromise) { const self = this; // keep a record of the current search id, so that if the search terms @@ -1266,9 +1260,9 @@ export default createReactClass({ searchInProgress: false, }); }); - }, + } - getSearchResultTiles: function() { + getSearchResultTiles() { const SearchResultTile = sdk.getComponent('rooms.SearchResultTile'); const Spinner = sdk.getComponent("elements.Spinner"); @@ -1348,20 +1342,20 @@ export default createReactClass({ onHeightChanged={onHeightChanged} />); } return ret; - }, + } - onPinnedClick: function() { + onPinnedClick = () => { const nowShowingPinned = !this.state.showingPinned; const roomId = this.state.room.roomId; this.setState({showingPinned: nowShowingPinned, searching: false}); SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned); - }, + }; - onSettingsClick: function() { + onSettingsClick = () => { dis.dispatch({ action: 'open_room_settings' }); - }, + }; - onCancelClick: function() { + onCancelClick = () => { console.log("updateTint from onCancelClick"); this.updateTint(); if (this.state.forwardingEvent) { @@ -1371,23 +1365,23 @@ export default createReactClass({ }); } dis.fire(Action.FocusComposer); - }, + }; - onLeaveClick: function() { + onLeaveClick = () => { dis.dispatch({ action: 'leave_room', room_id: this.state.room.roomId, }); - }, + }; - onForgetClick: function() { + onForgetClick = () => { dis.dispatch({ action: 'forget_room', room_id: this.state.room.roomId, }); - }, + }; - onRejectButtonClicked: function(ev) { + onRejectButtonClicked = ev => { const self = this; this.setState({ rejecting: true, @@ -1412,9 +1406,9 @@ export default createReactClass({ rejectError: error, }); }); - }, + }; - onRejectAndIgnoreClick: async function() { + onRejectAndIgnoreClick = async () => { this.setState({ rejecting: true, }); @@ -1446,49 +1440,49 @@ export default createReactClass({ rejectError: error, }); } - }, + }; - onRejectThreepidInviteButtonClicked: function(ev) { + onRejectThreepidInviteButtonClicked = ev => { // We can reject 3pid invites in the same way that we accept them, // using /leave rather than /join. In the short term though, we // just ignore them. // https://github.com/vector-im/vector-web/issues/1134 dis.fire(Action.ViewRoomDirectory); - }, + }; - onSearchClick: function() { + onSearchClick = () => { this.setState({ searching: !this.state.searching, showingPinned: false, }); - }, + }; - onCancelSearchClick: function() { + onCancelSearchClick = () => { this.setState({ searching: false, searchResults: null, }); - }, + }; // jump down to the bottom of this room, where new events are arriving - jumpToLiveTimeline: function() { + jumpToLiveTimeline = () => { this._messagePanel.jumpToLiveTimeline(); dis.fire(Action.FocusComposer); - }, + }; // jump up to wherever our read marker is - jumpToReadMarker: function() { + jumpToReadMarker = () => { this._messagePanel.jumpToReadMarker(); - }, + }; // update the read marker to match the read-receipt - forgetReadMarker: function(ev) { + forgetReadMarker = ev => { ev.stopPropagation(); this._messagePanel.forgetReadMarker(); - }, + }; // decide whether or not the top 'unread messages' bar should be shown - _updateTopUnreadMessagesBar: function() { + _updateTopUnreadMessagesBar = () => { if (!this._messagePanel) { return; } @@ -1497,12 +1491,12 @@ export default createReactClass({ if (this.state.showTopUnreadMessagesBar != showBar) { this.setState({showTopUnreadMessagesBar: showBar}); } - }, + }; // get the current scroll position of the room, so that it can be // restored when we switch back to it. // - _getScrollState: function() { + _getScrollState() { const messagePanel = this._messagePanel; if (!messagePanel) return null; @@ -1537,9 +1531,9 @@ export default createReactClass({ focussedEvent: scrollState.trackedScrollToken, pixelOffset: scrollState.pixelOffset, }; - }, + } - onResize: function() { + onResize = () => { // It seems flexbox doesn't give us a way to constrain the auxPanel height to have // a minimum of the height of the video element, whilst also capping it from pushing out the page // so we have to do it via JS instead. In this implementation we cap the height by putting @@ -1557,16 +1551,16 @@ export default createReactClass({ if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50; this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); - }, + }; - onFullscreenClick: function() { + onFullscreenClick = () => { dis.dispatch({ action: 'video_fullscreen', fullscreen: true, }, true); - }, + }; - onMuteAudioClick: function() { + onMuteAudioClick = () => { const call = this._getCallForRoom(); if (!call) { return; @@ -1574,9 +1568,9 @@ export default createReactClass({ const newState = !call.isMicrophoneMuted(); call.setMicrophoneMuted(newState); this.forceUpdate(); // TODO: just update the voip buttons - }, + }; - onMuteVideoClick: function() { + onMuteVideoClick = () => { const call = this._getCallForRoom(); if (!call) { return; @@ -1584,29 +1578,29 @@ export default createReactClass({ const newState = !call.isLocalVideoMuted(); call.setLocalVideoMuted(newState); this.forceUpdate(); // TODO: just update the voip buttons - }, + }; - onStatusBarVisible: function() { + onStatusBarVisible = () => { if (this.unmounted) return; this.setState({ statusBarVisible: true, }); - }, + }; - onStatusBarHidden: function() { + onStatusBarHidden = () => { // This is currently not desired as it is annoying if it keeps expanding and collapsing if (this.unmounted) return; this.setState({ statusBarVisible: false, }); - }, + }; /** * called by the parent component when PageUp/Down/etc is pressed. * * We pass it down to the scroll panel. */ - handleScrollKey: function(ev) { + handleScrollKey = ev => { let panel; if (this._searchResultsPanel.current) { panel = this._searchResultsPanel.current; @@ -1617,48 +1611,48 @@ export default createReactClass({ if (panel) { panel.handleScrollKey(ev); } - }, + }; /** * get any current call for this room */ - _getCallForRoom: function() { + _getCallForRoom() { if (!this.state.room) { return null; } return CallHandler.getCallForRoom(this.state.room.roomId); - }, + } // this has to be a proper method rather than an unnamed function, // otherwise react calls it with null on each update. - _gatherTimelinePanelRef: function(r) { + _gatherTimelinePanelRef = r => { this._messagePanel = r; if (r) { console.log("updateTint from RoomView._gatherTimelinePanelRef"); this.updateTint(); } - }, + }; - _getOldRoom: function() { + _getOldRoom() { const createEvent = this.state.room.currentState.getStateEvents("m.room.create", ""); if (!createEvent || !createEvent.getContent()['predecessor']) return null; return this.context.getRoom(createEvent.getContent()['predecessor']['room_id']); - }, + } - _getHiddenHighlightCount: function() { + _getHiddenHighlightCount() { const oldRoom = this._getOldRoom(); if (!oldRoom) return 0; return oldRoom.getUnreadNotificationCount('highlight'); - }, + } - _onHiddenHighlightsClick: function() { + _onHiddenHighlightsClick = () => { const oldRoom = this._getOldRoom(); if (!oldRoom) return; dis.dispatch({action: "view_room", room_id: oldRoom.roomId}); - }, + }; - render: function() { + render() { const RoomHeader = sdk.getComponent('rooms.RoomHeader'); const ForwardMessage = sdk.getComponent("rooms.ForwardMessage"); const AuxPanel = sdk.getComponent("rooms.AuxPanel"); @@ -2118,5 +2112,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 51113f4f568..ef21b5d787d 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -15,7 +15,6 @@ limitations under the License. */ import React, {createRef} from "react"; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import { Key } from '../../Keyboard'; import Timer from '../../utils/Timer'; @@ -84,10 +83,8 @@ if (DEBUG_SCROLL) { * offset as normal. */ -export default createReactClass({ - displayName: 'ScrollPanel', - - propTypes: { +export default class ScrollPanel extends React.Component { + static propTypes = { /* stickyBottom: if set to true, then once the user hits the bottom of * the list, any new children added to the list will cause the list to * scroll down to show the new element, rather than preserving the @@ -149,20 +146,19 @@ export default createReactClass({ * of the wrapper */ fixedChildren: PropTypes.node, - }, - - getDefaultProps: function() { - return { - stickyBottom: true, - startAtBottom: true, - onFillRequest: function(backwards) { return Promise.resolve(false); }, - onUnfillRequest: function(backwards, scrollToken) {}, - onScroll: function() {}, - }; - }, + }; + + static defaultProps = { + stickyBottom: true, + startAtBottom: true, + onFillRequest: function(backwards) { return Promise.resolve(false); }, + onUnfillRequest: function(backwards, scrollToken) {}, + onScroll: function() {}, + }; + + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this._pendingFillRequests = {b: null, f: null}; if (this.props.resizeNotifier) { @@ -172,13 +168,13 @@ export default createReactClass({ this.resetScrollState(); this._itemlist = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { this.checkScroll(); - }, + } - componentDidUpdate: function() { + componentDidUpdate() { // after adding event tiles, we may need to tweak the scroll (either to // keep at the bottom of the timeline, or to maintain the view after // adding events to the top). @@ -186,9 +182,9 @@ export default createReactClass({ // This will also re-check the fill state, in case the paginate was inadequate this.checkScroll(); this.updatePreventShrinking(); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { // set a boolean to say we've been unmounted, which any pending // promises can use to throw away their results. // @@ -198,41 +194,41 @@ export default createReactClass({ if (this.props.resizeNotifier) { this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize); } - }, + } - onScroll: function(ev) { + onScroll = ev => { debuglog("onScroll", this._getScrollNode().scrollTop); this._scrollTimeout.restart(); this._saveScrollState(); this.updatePreventShrinking(); this.props.onScroll(ev); this.checkFillState(); - }, + }; - onResize: function() { + onResize = () => { this.checkScroll(); // update preventShrinkingState if present if (this.preventShrinkingState) { this.preventShrinking(); } - }, + }; // after an update to the contents of the panel, check that the scroll is // where it ought to be, and set off pagination requests if necessary. - checkScroll: function() { + checkScroll = () => { if (this.unmounted) { return; } this._restoreSavedScrollState(); this.checkFillState(); - }, + }; // return true if the content is fully scrolled down right now; else false. // // note that this is independent of the 'stuckAtBottom' state - it is simply // about whether the content is scrolled down right now, irrespective of // whether it will stay that way when the children update. - isAtBottom: function() { + isAtBottom = () => { const sn = this._getScrollNode(); // fractional values (both too big and too small) // for scrollTop happen on certain browsers/platforms @@ -240,7 +236,7 @@ export default createReactClass({ // so check difference <= 1; return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1; - }, + }; // returns the vertical height in the given direction that can be removed from // the content box (which has a height of scrollHeight, see checkFillState) without @@ -273,7 +269,7 @@ export default createReactClass({ // |#########| - | // |#########| | // `---------' - - _getExcessHeight: function(backwards) { + _getExcessHeight(backwards) { const sn = this._getScrollNode(); const contentHeight = this._getMessagesHeight(); const listHeight = this._getListHeight(); @@ -285,10 +281,10 @@ export default createReactClass({ } else { return contentHeight - (unclippedScrollTop + 2*sn.clientHeight) - UNPAGINATION_PADDING; } - }, + } // check the scroll state and send out backfill requests if necessary. - checkFillState: async function(depth=0) { + checkFillState = async (depth=0) => { if (this.unmounted) { return; } @@ -368,10 +364,10 @@ export default createReactClass({ this._fillRequestWhileRunning = false; this.checkFillState(); } - }, + }; // check if unfilling is possible and send an unfill request if necessary - _checkUnfillState: function(backwards) { + _checkUnfillState(backwards) { let excessHeight = this._getExcessHeight(backwards); if (excessHeight <= 0) { return; @@ -417,10 +413,10 @@ export default createReactClass({ this.props.onUnfillRequest(backwards, markerScrollToken); }, UNFILL_REQUEST_DEBOUNCE_MS); } - }, + } // check if there is already a pending fill request. If not, set one off. - _maybeFill: function(depth, backwards) { + _maybeFill(depth, backwards) { const dir = backwards ? 'b' : 'f'; if (this._pendingFillRequests[dir]) { debuglog("Already a "+dir+" fill in progress - not starting another"); @@ -456,7 +452,7 @@ export default createReactClass({ return this.checkFillState(depth + 1); } }); - }, + } /* get the current scroll state. This returns an object with the following * properties: @@ -472,9 +468,7 @@ export default createReactClass({ * the number of pixels the bottom of the tracked child is above the * bottom of the scroll panel. */ - getScrollState: function() { - return this.scrollState; - }, + getScrollState = () => this.scrollState; /* reset the saved scroll state. * @@ -488,7 +482,7 @@ export default createReactClass({ * no use if no children exist yet, or if you are about to replace the * child list.) */ - resetScrollState: function() { + resetScrollState = () => { this.scrollState = { stuckAtBottom: this.props.startAtBottom, }; @@ -496,20 +490,20 @@ export default createReactClass({ this._pages = 0; this._scrollTimeout = new Timer(100); this._heightUpdateInProgress = false; - }, + }; /** * jump to the top of the content. */ - scrollToTop: function() { + scrollToTop = () => { this._getScrollNode().scrollTop = 0; this._saveScrollState(); - }, + }; /** * jump to the bottom of the content. */ - scrollToBottom: function() { + scrollToBottom = () => { // the easiest way to make sure that the scroll state is correctly // saved is to do the scroll, then save the updated state. (Calculating // it ourselves is hard, and we can't rely on an onScroll callback @@ -517,25 +511,25 @@ export default createReactClass({ const sn = this._getScrollNode(); sn.scrollTop = sn.scrollHeight; this._saveScrollState(); - }, + }; /** * Page up/down. * * @param {number} mult: -1 to page up, +1 to page down */ - scrollRelative: function(mult) { + scrollRelative = mult => { const scrollNode = this._getScrollNode(); const delta = mult * scrollNode.clientHeight * 0.5; scrollNode.scrollBy(0, delta); this._saveScrollState(); - }, + }; /** * Scroll up/down in response to a scroll key * @param {object} ev the keyboard event */ - handleScrollKey: function(ev) { + handleScrollKey = ev => { switch (ev.key) { case Key.PAGE_UP: if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { @@ -561,7 +555,7 @@ export default createReactClass({ } break; } - }, + }; /* Scroll the panel to bring the DOM node with the scroll token * `scrollToken` into view. @@ -574,7 +568,7 @@ export default createReactClass({ * node (specifically, the bottom of it) will be positioned. If omitted, it * defaults to 0. */ - scrollToToken: function(scrollToken, pixelOffset, offsetBase) { + scrollToToken = (scrollToken, pixelOffset, offsetBase) => { pixelOffset = pixelOffset || 0; offsetBase = offsetBase || 0; @@ -596,9 +590,9 @@ export default createReactClass({ scrollNode.scrollTop = (trackedNode.offsetTop - (scrollNode.clientHeight * offsetBase)) + pixelOffset; this._saveScrollState(); } - }, + }; - _saveScrollState: function() { + _saveScrollState() { if (this.props.stickyBottom && this.isAtBottom()) { this.scrollState = { stuckAtBottom: true }; debuglog("saved stuckAtBottom state"); @@ -641,9 +635,9 @@ export default createReactClass({ bottomOffset: bottomOffset, pixelOffset: bottomOffset - viewportBottom, //needed for restoring the scroll position when coming back to the room }; - }, + } - _restoreSavedScrollState: async function() { + async _restoreSavedScrollState() { const scrollState = this.scrollState; if (scrollState.stuckAtBottom) { @@ -676,7 +670,8 @@ export default createReactClass({ } else { debuglog("not updating height because request already in progress"); } - }, + } + // need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content? async _updateHeight() { // wait until user has stopped scrolling @@ -731,7 +726,7 @@ export default createReactClass({ debuglog("updateHeight to", {newHeight, topDiff}); } } - }, + } _getTrackedNode() { const scrollState = this.scrollState; @@ -764,11 +759,11 @@ export default createReactClass({ } return scrollState.trackedNode; - }, + } _getListHeight() { return this._bottomGrowth + (this._pages * PAGE_SIZE); - }, + } _getMessagesHeight() { const itemlist = this._itemlist.current; @@ -777,17 +772,17 @@ export default createReactClass({ const firstNodeTop = itemlist.firstElementChild ? itemlist.firstElementChild.offsetTop : 0; // 18 is itemlist padding return lastNodeBottom - firstNodeTop + (18 * 2); - }, + } _topFromBottom(node) { // current capped height - distance from top = distance from bottom of container to top of tracked element return this._itemlist.current.clientHeight - node.offsetTop; - }, + } /* get the DOM node which has the scrollTop property we care about for our * message panel. */ - _getScrollNode: function() { + _getScrollNode() { if (this.unmounted) { // this shouldn't happen, but when it does, turn the NPE into // something more meaningful. @@ -801,18 +796,18 @@ export default createReactClass({ } return this._divScroll; - }, + } - _collectScroll: function(divScroll) { + _collectScroll = divScroll => { this._divScroll = divScroll; - }, + }; /** Mark the bottom offset of the last tile so we can balance it out when anything below it changes, by calling updatePreventShrinking, to keep the same minimum bottom offset, effectively preventing the timeline to shrink. */ - preventShrinking: function() { + preventShrinking = () => { const messageList = this._itemlist.current; const tiles = messageList && messageList.children; if (!messageList) { @@ -836,16 +831,16 @@ export default createReactClass({ offsetNode: lastTileNode, }; debuglog("prevent shrinking, last tile ", offsetFromBottom, "px from bottom"); - }, + }; /** Clear shrinking prevention. Used internally, and when the timeline is reloaded. */ - clearPreventShrinking: function() { + clearPreventShrinking = () => { const messageList = this._itemlist.current; const balanceElement = messageList && messageList.parentElement; if (balanceElement) balanceElement.style.paddingBottom = null; this.preventShrinkingState = null; debuglog("prevent shrinking cleared"); - }, + }; /** update the container padding to balance @@ -855,7 +850,7 @@ export default createReactClass({ from the bottom of the marked tile grows larger than what it was when marking. */ - updatePreventShrinking: function() { + updatePreventShrinking = () => { if (this.preventShrinkingState) { const sn = this._getScrollNode(); const scrollState = this.scrollState; @@ -885,9 +880,9 @@ export default createReactClass({ this.clearPreventShrinking(); } } - }, + }; - render: function() { + render() { // TODO: the classnames on the div and ol could do with being updated to // reflect the fact that we don't necessarily contain a list of messages. // it's not obvious why we have a separate div and ol anyway. @@ -905,5 +900,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/structures/SearchBox.js b/src/components/structures/SearchBox.js index 7e9d290bce1..ed118466c62 100644 --- a/src/components/structures/SearchBox.js +++ b/src/components/structures/SearchBox.js @@ -16,7 +16,6 @@ limitations under the License. */ import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import { Key } from '../../Keyboard'; import dis from '../../dispatcher/dispatcher'; @@ -24,10 +23,8 @@ import { throttle } from 'lodash'; import AccessibleButton from '../../components/views/elements/AccessibleButton'; import classNames from 'classnames'; -export default createReactClass({ - displayName: 'SearchBox', - - propTypes: { +export default class SearchBox extends React.Component { + static propTypes = { onSearch: PropTypes.func, onCleared: PropTypes.func, onKeyDown: PropTypes.func, @@ -38,35 +35,32 @@ export default createReactClass({ // on room search focus action (it would be nicer to take // this functionality out, but not obvious how that would work) enableRoomSearchFocus: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - enableRoomSearchFocus: false, - }; - }, + static defaultProps = { + enableRoomSearchFocus: false, + }; - getInitialState: function() { - return { + constructor(props) { + super(props); + + this._search = createRef(); + + this.state = { searchTerm: "", blurred: true, }; - }, - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { - this._search = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { this.dispatcherRef = dis.register(this.onAction); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { dis.unregister(this.dispatcherRef); - }, + } - onAction: function(payload) { + onAction = payload => { if (!this.props.enableRoomSearchFocus) return; switch (payload.action) { @@ -81,51 +75,51 @@ export default createReactClass({ } break; } - }, + }; - onChange: function() { + onChange = () => { if (!this._search.current) return; this.setState({ searchTerm: this._search.current.value }); this.onSearch(); - }, + }; - onSearch: throttle(function() { + onSearch = throttle(() => { this.props.onSearch(this._search.current.value); - }, 200, {trailing: true, leading: true}), + }, 200, {trailing: true, leading: true}); - _onKeyDown: function(ev) { + _onKeyDown = ev => { switch (ev.key) { case Key.ESCAPE: this._clearSearch("keyboard"); break; } if (this.props.onKeyDown) this.props.onKeyDown(ev); - }, + }; - _onFocus: function(ev) { + _onFocus = ev => { this.setState({blurred: false}); ev.target.select(); if (this.props.onFocus) { this.props.onFocus(ev); } - }, + }; - _onBlur: function(ev) { + _onBlur = ev => { this.setState({blurred: true}); if (this.props.onBlur) { this.props.onBlur(ev); } - }, + }; - _clearSearch: function(source) { + _clearSearch(source) { this._search.current.value = ""; this.onChange(); if (this.props.onCleared) { this.props.onCleared(source); } - }, + } - render: function() { + render() { // check for collapsed here and // not at parent so we keep // searchTerm in our state @@ -166,5 +160,5 @@ export default createReactClass({ { clearButton } ); - }, -}); + } +} diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js index a714b126ec5..135b2a1c5ca 100644 --- a/src/components/structures/TagPanel.js +++ b/src/components/structures/TagPanel.js @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import TagOrderStore from '../../stores/TagOrderStore'; import GroupActions from '../../actions/GroupActions'; @@ -32,21 +31,15 @@ import AutoHideScrollbar from "./AutoHideScrollbar"; import SettingsStore from "../../settings/SettingsStore"; import UserTagTile from "../views/elements/UserTagTile"; -const TagPanel = createReactClass({ - displayName: 'TagPanel', +class TagPanel extends React.Component { + static contextType = MatrixClientContext; - statics: { - contextType: MatrixClientContext, - }, + state = { + orderedTags: [], + selectedTags: [], + }; - getInitialState() { - return { - orderedTags: [], - selectedTags: [], - }; - }, - - componentDidMount: function() { + componentDidMount() { this.unmounted = false; this.context.on("Group.myMembership", this._onGroupMyMembership); this.context.on("sync", this._onClientSync); @@ -62,7 +55,7 @@ const TagPanel = createReactClass({ }); // This could be done by anything with a matrix client dis.dispatch(GroupActions.fetchJoinedGroups(this.context)); - }, + } componentWillUnmount() { this.unmounted = true; @@ -71,14 +64,14 @@ const TagPanel = createReactClass({ if (this._tagOrderStoreToken) { this._tagOrderStoreToken.remove(); } - }, + } - _onGroupMyMembership() { + _onGroupMyMembership = () => { if (this.unmounted) return; dis.dispatch(GroupActions.fetchJoinedGroups(this.context)); - }, + }; - _onClientSync(syncState, prevState) { + _onClientSync = (syncState, prevState) => { // Consider the client reconnected if there is no error with syncing. // This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP. const reconnected = syncState !== "ERROR" && prevState !== syncState; @@ -86,18 +79,18 @@ const TagPanel = createReactClass({ // Load joined groups dis.dispatch(GroupActions.fetchJoinedGroups(this.context)); } - }, + }; - onMouseDown(e) { + onMouseDown = e => { // only dispatch if its not a no-op if (this.state.selectedTags.length > 0) { dis.dispatch({action: 'deselect_tags'}); } - }, + }; - onClearFilterClick(ev) { + onClearFilterClick = ev => { dis.dispatch({action: 'deselect_tags'}); - }, + }; renderGlobalIcon() { if (!SettingsStore.getValue("feature_communities_v2_prototypes")) return null; @@ -108,7 +101,7 @@ const TagPanel = createReactClass({
); - }, + } render() { const DNDTagTile = sdk.getComponent('elements.DNDTagTile'); @@ -173,6 +166,6 @@ const TagPanel = createReactClass({ ; - }, -}); + } +} export default TagPanel; diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 2313b60ab1c..d9cec2e0540 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -19,7 +19,6 @@ limitations under the License. import SettingsStore from "../../settings/SettingsStore"; import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import ReactDOM from "react-dom"; import PropTypes from 'prop-types'; import {EventTimeline} from "matrix-js-sdk"; @@ -54,10 +53,8 @@ if (DEBUG) { * * Also responsible for handling and sending read receipts. */ -const TimelinePanel = createReactClass({ - displayName: 'TimelinePanel', - - propTypes: { +class TimelinePanel extends React.Component { + static propTypes = { // The js-sdk EventTimelineSet object for the timeline sequence we are // representing. This may or may not have a room, depending on what it's // a timeline representing. If it has a room, we maintain RRs etc for @@ -115,23 +112,35 @@ const TimelinePanel = createReactClass({ // whether to use the irc layout useIRCLayout: PropTypes.bool, - }, + } - statics: { - // a map from room id to read marker event timestamp - roomReadMarkerTsMap: {}, - }, + // a map from room id to read marker event timestamp + static roomReadMarkerTsMap = {}; - getDefaultProps: function() { - return { - // By default, disable the timelineCap in favour of unpaginating based on - // event tile heights. (See _unpaginateEvents) - timelineCap: Number.MAX_VALUE, - className: 'mx_RoomView_messagePanel', - }; - }, + static defaultProps = { + // By default, disable the timelineCap in favour of unpaginating based on + // event tile heights. (See _unpaginateEvents) + timelineCap: Number.MAX_VALUE, + className: 'mx_RoomView_messagePanel', + }; + + constructor(props) { + super(props); + + debuglog("TimelinePanel: mounting"); + + this.lastRRSentEventId = undefined; + this.lastRMSentEventId = undefined; + + this._messagePanel = createRef(); + + 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. // but for now we just do it per room for simplicity. let initialReadMarker = null; @@ -144,7 +153,7 @@ const TimelinePanel = createReactClass({ } } - return { + this.state = { events: [], liveEvents: [], 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 readMarkerOutOfViewThresholdMs: SettingsStore.getValue("readMarkerOutOfViewThresholdMs"), }; - }, - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { - debuglog("TimelinePanel: mounting"); - - this.lastRRSentEventId = undefined; - this.lastRMSentEventId = undefined; - - this._messagePanel = createRef(); - - if (this.props.manageReadReceipts) { - this.updateReadReceiptOnUserActivity(); - } - if (this.props.manageReadMarkers) { - this.updateReadMarkerOnUserActivity(); - } - this.dispatcherRef = dis.register(this.onAction); MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); @@ -236,10 +227,10 @@ const TimelinePanel = createReactClass({ MatrixClientPeg.get().on("sync", this.onSync); this._initTimeline(this.props); - }, + } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps: function(newProps) { + UNSAFE_componentWillReceiveProps(newProps) { if (newProps.timelineSet !== this.props.timelineSet) { // throw new Error("changing timelineSet on a TimelinePanel is not supported"); @@ -260,9 +251,9 @@ const TimelinePanel = createReactClass({ " (was " + this.props.eventId + ")"); return this._initTimeline(newProps); } - }, + } - shouldComponentUpdate: function(nextProps, nextState) { + shouldComponentUpdate(nextProps, nextState) { if (!ObjectUtils.shallowEqual(this.props, nextProps)) { if (DEBUG) { console.group("Timeline.shouldComponentUpdate: props change"); @@ -284,9 +275,9 @@ const TimelinePanel = createReactClass({ } return false; - }, + } - componentWillUnmount: function() { + componentWillUnmount() { // set a boolean to say we've been unmounted, which any pending // promises can use to throw away their results. // @@ -316,9 +307,9 @@ const TimelinePanel = createReactClass({ client.removeListener("Event.replaced", this.onEventReplaced); client.removeListener("sync", this.onSync); } - }, + } - onMessageListUnfillRequest: function(backwards, scrollToken) { + onMessageListUnfillRequest = (backwards, scrollToken) => { // If backwards, unpaginate from the back (i.e. the start of the timeline) const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS; debuglog("TimelinePanel: unpaginating events in direction", dir); @@ -349,18 +340,18 @@ const TimelinePanel = createReactClass({ firstVisibleEventIndex, }); } - }, + }; - onPaginationRequest(timelineWindow, direction, size) { + onPaginationRequest = (timelineWindow, direction, size) => { if (this.props.onPaginationRequest) { return this.props.onPaginationRequest(timelineWindow, direction, size); } else { return timelineWindow.paginate(direction, size); } - }, + }; // set off a pagination request. - onMessageListFillRequest: function(backwards) { + onMessageListFillRequest = backwards => { if (!this._shouldPaginate()) return Promise.resolve(false); const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS; @@ -425,9 +416,9 @@ const TimelinePanel = createReactClass({ }); }); }); - }, + }; - onMessageListScroll: function(e) { + onMessageListScroll = e => { if (this.props.onScroll) { this.props.onScroll(e); } @@ -447,9 +438,9 @@ const TimelinePanel = createReactClass({ // NO-OP when timeout already has set to the given value this._readMarkerActivityTimer.changeTimeout(timeout); } - }, + }; - onAction: function(payload) { + onAction = payload => { if (payload.action === 'ignore_state_changed') { this.forceUpdate(); } @@ -463,9 +454,9 @@ const TimelinePanel = createReactClass({ } }); } - }, + }; - onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) { + onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => { // ignore events for other timeline sets if (data.timeline.getTimelineSet() !== this.props.timelineSet) return; @@ -537,21 +528,19 @@ const TimelinePanel = createReactClass({ } }); }); - }, + }; - onRoomTimelineReset: function(room, timelineSet) { + onRoomTimelineReset = (room, timelineSet) => { if (timelineSet !== this.props.timelineSet) return; if (this._messagePanel.current && this._messagePanel.current.isAtBottom()) { this._loadTimeline(); } - }, + }; - canResetTimeline: function() { - return this._messagePanel.current && this._messagePanel.current.isAtBottom(); - }, + canResetTimeline = () => this._messagePanel.current && this._messagePanel.current.isAtBottom(); - onRoomRedaction: function(ev, room) { + onRoomRedaction = (ev, room) => { if (this.unmounted) return; // ignore events for other rooms @@ -560,9 +549,9 @@ const TimelinePanel = createReactClass({ // we could skip an update if the event isn't in our timeline, // but that's probably an early optimisation. this.forceUpdate(); - }, + }; - onEventReplaced: function(replacedEvent, room) { + onEventReplaced = (replacedEvent, room) => { if (this.unmounted) return; // ignore events for other rooms @@ -571,27 +560,27 @@ const TimelinePanel = createReactClass({ // we could skip an update if the event isn't in our timeline, // but that's probably an early optimisation. this.forceUpdate(); - }, + }; - onRoomReceipt: function(ev, room) { + onRoomReceipt = (ev, room) => { if (this.unmounted) return; // ignore events for other rooms if (room !== this.props.timelineSet.room) return; this.forceUpdate(); - }, + }; - onLocalEchoUpdated: function(ev, room, oldEventId) { + onLocalEchoUpdated = (ev, room, oldEventId) => { if (this.unmounted) return; // ignore events for other rooms if (room !== this.props.timelineSet.room) return; this._reloadEvents(); - }, + }; - onAccountData: function(ev, room) { + onAccountData = (ev, room) => { if (this.unmounted) return; // ignore events for other rooms @@ -605,9 +594,9 @@ const TimelinePanel = createReactClass({ this.setState({ readMarkerEventId: ev.getContent().event_id, }, this.props.onReadMarkerUpdated); - }, + }; - onEventDecrypted: function(ev) { + onEventDecrypted = ev => { // Can be null for the notification timeline, etc. if (!this.props.timelineSet.room) return; @@ -620,19 +609,19 @@ const TimelinePanel = createReactClass({ if (ev.getRoomId() === this.props.timelineSet.room.roomId) { this.forceUpdate(); } - }, + }; - onSync: function(state, prevState, data) { + onSync = (state, prevState, data) => { this.setState({clientSyncState: state}); - }, + }; _readMarkerTimeout(readMarkerPosition) { return readMarkerPosition === 0 ? this.state.readMarkerInViewThresholdMs : this.state.readMarkerOutOfViewThresholdMs; - }, + } - updateReadMarkerOnUserActivity: async function() { + async updateReadMarkerOnUserActivity() { const initialTimeout = this._readMarkerTimeout(this.getReadMarkerPosition()); this._readMarkerActivityTimer = new Timer(initialTimeout); @@ -644,9 +633,9 @@ const TimelinePanel = createReactClass({ // outside of try/catch to not swallow errors this.updateReadMarker(); } - }, + } - updateReadReceiptOnUserActivity: async function() { + async updateReadReceiptOnUserActivity() { this._readReceiptActivityTimer = new Timer(READ_RECEIPT_INTERVAL_MS); while (this._readReceiptActivityTimer) { //unset on unmount UserActivity.sharedInstance().timeWhileActiveNow(this._readReceiptActivityTimer); @@ -656,9 +645,9 @@ const TimelinePanel = createReactClass({ // outside of try/catch to not swallow errors this.sendReadReceipt(); } - }, + } - sendReadReceipt: function() { + sendReadReceipt = () => { if (SettingsStore.getValue("lowBandwidth")) return; if (!this._messagePanel.current) return; @@ -766,11 +755,11 @@ const TimelinePanel = createReactClass({ }); } } - }, + }; // if the read marker is on the screen, we can now assume we've caught up to the end // of the screen, so move the marker down to the bottom of the screen. - updateReadMarker: function() { + updateReadMarker = () => { if (!this.props.manageReadMarkers) return; if (this.getReadMarkerPosition() === 1) { // the read marker is at an event below the viewport, @@ -801,11 +790,11 @@ const TimelinePanel = createReactClass({ // Send the updated read marker (along with read receipt) to the server this.sendReadReceipt(); - }, + }; // advance the read marker past any events we sent ourselves. - _advanceReadMarkerPastMyEvents: function() { + _advanceReadMarkerPastMyEvents() { if (!this.props.manageReadMarkers) return; // we call `_timelineWindow.getEvents()` rather than using @@ -837,11 +826,11 @@ const TimelinePanel = createReactClass({ const ev = events[i]; this._setReadMarker(ev.getId(), ev.getTs()); - }, + } /* jump down to the bottom of this room, where new events are arriving */ - jumpToLiveTimeline: function() { + jumpToLiveTimeline = () => { // if we can't forward-paginate the existing timeline, then there // is no point reloading it - just jump straight to the bottom. // @@ -854,12 +843,12 @@ const TimelinePanel = createReactClass({ this._messagePanel.current.scrollToBottom(); } } - }, + }; /* scroll to show the read-up-to marker. We put it 1/3 of the way down * the container. */ - jumpToReadMarker: function() { + jumpToReadMarker = () => { if (!this.props.manageReadMarkers) return; if (!this._messagePanel.current) return; if (!this.state.readMarkerEventId) return; @@ -883,11 +872,11 @@ const TimelinePanel = createReactClass({ // As with jumpToLiveTimeline, we want to reload the timeline around the // read-marker. this._loadTimeline(this.state.readMarkerEventId, 0, 1/3); - }, + }; /* update the read-up-to marker to match the read receipt */ - forgetReadMarker: function() { + forgetReadMarker = () => { if (!this.props.manageReadMarkers) return; const rmId = this._getCurrentReadReceipt(); @@ -903,17 +892,17 @@ const TimelinePanel = createReactClass({ } this._setReadMarker(rmId, rmTs); - }, + }; /* return true if the content is fully scrolled down and we are * at the end of the live timeline. */ - isAtEndOfLiveTimeline: function() { + isAtEndOfLiveTimeline = () => { return this._messagePanel.current && this._messagePanel.current.isAtBottom() && this._timelineWindow && !this._timelineWindow.canPaginate(EventTimeline.FORWARDS); - }, + } /* get the current scroll state. See ScrollPanel.getScrollState for @@ -921,10 +910,10 @@ const TimelinePanel = createReactClass({ * * returns null if we are not mounted. */ - getScrollState: function() { + getScrollState = () => { if (!this._messagePanel.current) { return null; } return this._messagePanel.current.getScrollState(); - }, + }; // returns one of: // @@ -932,7 +921,7 @@ const TimelinePanel = createReactClass({ // -1: read marker is above the window // 0: read marker is visible // +1: read marker is below the window - getReadMarkerPosition: function() { + getReadMarkerPosition = () => { if (!this.props.manageReadMarkers) return null; if (!this._messagePanel.current) return null; @@ -953,9 +942,9 @@ const TimelinePanel = createReactClass({ } return null; - }, + }; - canJumpToReadMarker: function() { + canJumpToReadMarker = () => { // 1. Do not show jump bar if neither the RM nor the RR are set. // 3. We want to show the bar if the read-marker is off the top of the screen. // 4. Also, if pos === null, the event might not be paginated - show the unread bar @@ -963,14 +952,14 @@ const TimelinePanel = createReactClass({ const ret = this.state.readMarkerEventId !== null && // 1. (pos < 0 || pos === null); // 3., 4. return ret; - }, + }; /* * called by the parent component when PageUp/Down/etc is pressed. * * We pass it down to the scroll panel. */ - handleScrollKey: function(ev) { + handleScrollKey = ev => { if (!this._messagePanel.current) { return; } // jump to the live timeline on ctrl-end, rather than the end of the @@ -980,9 +969,9 @@ const TimelinePanel = createReactClass({ } else { this._messagePanel.current.handleScrollKey(ev); } - }, + }; - _initTimeline: function(props) { + _initTimeline(props) { const initialEvent = props.eventId; const pixelOffset = props.eventPixelOffset; @@ -994,7 +983,7 @@ const TimelinePanel = createReactClass({ } return this._loadTimeline(initialEvent, pixelOffset, offsetBase); - }, + } /** * (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. */ - _loadTimeline: function(eventId, pixelOffset, offsetBase) { + _loadTimeline(eventId, pixelOffset, offsetBase) { this._timelineWindow = new Matrix.TimelineWindow( MatrixClientPeg.get(), this.props.timelineSet, {windowLimit: this.props.timelineCap}); @@ -1122,21 +1111,21 @@ const TimelinePanel = createReactClass({ }); prom.then(onLoaded, onError); } - }, + } // handle the completion of a timeline load or localEchoUpdate, by // reloading the events from the timelinewindow and pending event list into // the state. - _reloadEvents: function() { + _reloadEvents() { // we might have switched rooms since the load started - just bin // the results if so. if (this.unmounted) return; this.setState(this._getEvents()); - }, + } // get the list of events from the timeline window and the pending event list - _getEvents: function() { + _getEvents() { const events = this._timelineWindow.getEvents(); const firstVisibleEventIndex = this._checkForPreJoinUISI(events); @@ -1154,7 +1143,7 @@ const TimelinePanel = createReactClass({ liveEvents, firstVisibleEventIndex, }; - }, + } /** * 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 * such events were found, then it returns 0. */ - _checkForPreJoinUISI: function(events) { + _checkForPreJoinUISI(events) { const room = this.props.timelineSet.room; if (events.length === 0 || !room || @@ -1228,18 +1217,18 @@ const TimelinePanel = createReactClass({ } } return 0; - }, + } - _indexForEventId: function(evId) { + _indexForEventId(evId) { for (let i = 0; i < this.state.events.length; ++i) { if (evId == this.state.events[i].getId()) { return i; } } return null; - }, + } - _getLastDisplayedEventIndex: function(opts) { + _getLastDisplayedEventIndex(opts) { opts = opts || {}; const ignoreOwn = opts.ignoreOwn || false; const allowPartial = opts.allowPartial || false; @@ -1313,7 +1302,7 @@ const TimelinePanel = createReactClass({ } return null; - }, + } /** * Get the id of the event corresponding to our user's latest read-receipt. @@ -1324,7 +1313,7 @@ const TimelinePanel = createReactClass({ * SDK. * @return {String} the event ID */ - _getCurrentReadReceipt: function(ignoreSynthesized) { + _getCurrentReadReceipt(ignoreSynthesized) { const client = MatrixClientPeg.get(); // the client can be null on logout if (client == null) { @@ -1333,9 +1322,9 @@ const TimelinePanel = createReactClass({ const myUserId = client.credentials.userId; return this.props.timelineSet.room.getEventReadUpTo(myUserId, ignoreSynthesized); - }, + } - _setReadMarker: function(eventId, eventTs, inhibitSetState) { + _setReadMarker(eventId, eventTs, inhibitSetState) { const roomId = this.props.timelineSet.room.roomId; // don't update the state (and cause a re-render) if there is @@ -1358,9 +1347,9 @@ const TimelinePanel = createReactClass({ this.setState({ readMarkerEventId: eventId, }, this.props.onReadMarkerUpdated); - }, + } - _shouldPaginate: function() { + _shouldPaginate() { // don't try to paginate while events in the timeline are // still being decrypted. We don't render events while they're // being decrypted, so they don't take up space in the timeline. @@ -1369,13 +1358,13 @@ const TimelinePanel = createReactClass({ return !this.state.events.some((e) => { return e.isBeingDecrypted(); }); - }, + } getRelationsForEvent(...args) { return this.props.timelineSet.getRelationsForEvent(...args); - }, + } - render: function() { + render() { const MessagePanel = sdk.getComponent("structures.MessagePanel"); const Loader = sdk.getComponent("elements.Spinner"); @@ -1456,7 +1445,7 @@ const TimelinePanel = createReactClass({ useIRCLayout={this.props.useIRCLayout} /> ); - }, -}); + } +} export default TimelinePanel; diff --git a/src/components/structures/UploadBar.js b/src/components/structures/UploadBar.js index 421d1d79a74..0865764c5a7 100644 --- a/src/components/structures/UploadBar.js +++ b/src/components/structures/UploadBar.js @@ -16,30 +16,28 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import ContentMessages from '../../ContentMessages'; import dis from "../../dispatcher/dispatcher"; import filesize from "filesize"; import { _t } from '../../languageHandler'; -export default createReactClass({ - displayName: 'UploadBar', - propTypes: { +export default class UploadBar extends React.Component { + static propTypes = { room: PropTypes.object, - }, + }; - componentDidMount: function() { + componentDidMount() { this.dispatcherRef = dis.register(this.onAction); this.mounted = true; - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this.mounted = false; dis.unregister(this.dispatcherRef); - }, + } - onAction: function(payload) { + onAction = payload => { switch (payload.action) { case 'upload_progress': case 'upload_finished': @@ -48,9 +46,9 @@ export default createReactClass({ if (this.mounted) this.forceUpdate(); break; } - }, + }; - render: function() { + render() { const uploads = ContentMessages.sharedInstance().getCurrentUploads(); // for testing UI... - also fix up the ContentMessages.getCurrentUploads().length @@ -105,5 +103,5 @@ export default createReactClass({
{ uploadText }
); - }, -}); + } +} diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index 326ba2c22f0..0b969784e5e 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -17,24 +17,21 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import SyntaxHighlight from '../views/elements/SyntaxHighlight'; import {_t} from "../../languageHandler"; import * as sdk from "../../index"; -export default createReactClass({ - displayName: 'ViewSource', - - propTypes: { +export default class ViewSource extends React.Component { + static propTypes = { content: PropTypes.object.isRequired, onFinished: PropTypes.func.isRequired, roomId: PropTypes.string.isRequired, eventId: PropTypes.string.isRequired, - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( @@ -49,5 +46,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js index 9877c531064..3fa2713a35f 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; @@ -40,50 +39,47 @@ const PHASE_EMAIL_SENT = 3; // User has clicked the link in email and completed reset const PHASE_DONE = 4; -export default createReactClass({ - displayName: 'ForgotPassword', - - propTypes: { +export default class ForgotPassword extends React.Component { + static propTypes = { serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, onServerConfigChange: PropTypes.func.isRequired, onLoginClick: PropTypes.func, onComplete: PropTypes.func.isRequired, - }, - - getInitialState: function() { - return { - phase: PHASE_FORGOT, - email: "", - password: "", - password2: "", - errorText: null, - - // We perform liveliness checks later, but for now suppress the errors. - // 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 - // be seeing. - serverIsAlive: true, - serverErrorIsFatal: false, - serverDeadError: "", - serverRequiresIdServer: null, - }; - }, - - componentDidMount: function() { + }; + + state = { + phase: PHASE_FORGOT, + email: "", + password: "", + password2: "", + errorText: null, + + // We perform liveliness checks later, but for now suppress the errors. + // 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 + // be seeing. + serverIsAlive: true, + serverErrorIsFatal: false, + serverDeadError: "", + serverRequiresIdServer: null, + }; + + componentDidMount() { this.reset = null; this._checkServerLiveliness(this.props.serverConfig); - }, + } // 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 && newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return; // Do a liveliness check on the new URLs this._checkServerLiveliness(newProps.serverConfig); - }, + } - _checkServerLiveliness: async function(serverConfig) { + async _checkServerLiveliness(serverConfig) { try { await AutoDiscoveryUtils.validateServerConfigWithStaticUrls( serverConfig.hsUrl, @@ -100,9 +96,9 @@ export default createReactClass({ } catch (e) { this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password")); } - }, + } - submitPasswordReset: function(email, password) { + submitPasswordReset(email, password) { this.setState({ phase: PHASE_SENDING_EMAIL, }); @@ -117,9 +113,9 @@ export default createReactClass({ phase: PHASE_FORGOT, }); }); - }, + } - onVerify: async function(ev) { + onVerify = async ev => { ev.preventDefault(); if (!this.reset) { console.error("onVerify called before submitPasswordReset!"); @@ -131,9 +127,9 @@ export default createReactClass({ } catch (err) { this.showErrorDialog(err.message); } - }, + }; - onSubmitForm: async function(ev) { + onSubmitForm = async ev => { ev.preventDefault(); // 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({ [stateKey]: ev.target.value, }); - }, + }; - async onServerDetailsNextPhaseClick() { + onServerDetailsNextPhaseClick = async () => { this.setState({ phase: PHASE_FORGOT, }); - }, + }; - onEditServerDetailsClick(ev) { + onEditServerDetailsClick = ev => { ev.preventDefault(); ev.stopPropagation(); this.setState({ phase: PHASE_SERVER_DETAILS, }); - }, + }; - onLoginClick: function(ev) { + onLoginClick = ev => { ev.preventDefault(); ev.stopPropagation(); this.props.onLoginClick(); - }, + }; - showErrorDialog: function(body, title) { + showErrorDialog(body, title) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, { title: title, description: body, }); - }, + } renderServerDetails() { const ServerConfig = sdk.getComponent("auth.ServerConfig"); @@ -218,7 +214,7 @@ export default createReactClass({ submitText={_t("Next")} submitClass="mx_Login_submit" />; - }, + } renderForgot() { const Field = sdk.getComponent('elements.Field'); @@ -335,12 +331,12 @@ export default createReactClass({ {_t('Sign in instead')} ; - }, + } renderSendingEmail() { const Spinner = sdk.getComponent("elements.Spinner"); return ; - }, + } renderEmailSent() { return
@@ -350,7 +346,7 @@ export default createReactClass({
; - }, + } renderDone() { return
@@ -363,9 +359,9 @@ export default createReactClass({
; - }, + } - render: function() { + render() { const AuthHeader = sdk.getComponent("auth.AuthHeader"); const AuthBody = sdk.getComponent("auth.AuthBody"); @@ -397,5 +393,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js index 3bc363f863f..e56a9ea5e90 100644 --- a/src/components/structures/auth/Login.js +++ b/src/components/structures/auth/Login.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import {_t, _td} from '../../../languageHandler'; import * as sdk from '../../../index'; @@ -56,10 +55,8 @@ _td("General failure"); /** * A wire component which glues together login UI components and Login logic */ -export default createReactClass({ - displayName: 'Login', - - propTypes: { +export default class Login extends React.Component { + static propTypes = { // Called when the user has logged in. Params: // - The object returned by the login API // - 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, isSyncing: PropTypes.bool, - }, + }; + + constructor(props) { + super(props); + + this._unmounted = false; - getInitialState: function() { - return { + this.state = { busy: false, busyLoggingIn: null, errorText: null, @@ -113,11 +114,6 @@ export default createReactClass({ serverErrorIsFatal: false, 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 // letting you do that login type @@ -130,11 +126,11 @@ export default createReactClass({ }; this._initLoginLogic(); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this._unmounted = true; - }, + } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event UNSAFE_componentWillReceiveProps(newProps) { @@ -143,20 +139,18 @@ export default createReactClass({ // Ensure that we end up actually logging in to the right place this._initLoginLogic(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl); - }, + } - onPasswordLoginError: function(errorText) { + onPasswordLoginError = errorText => { this.setState({ errorText, loginIncorrect: Boolean(errorText), }); - }, + }; - isBusy: function() { - return this.state.busy || this.props.busy; - }, + isBusy = () => this.state.busy || this.props.busy; - onPasswordLogin: async function(username, phoneCountry, phoneNumber, password) { + onPasswordLogin = async (username, phoneCountry, phoneNumber, password) => { if (!this.state.serverIsAlive) { this.setState({busy: true}); // Do a quick liveliness check on the URLs @@ -263,13 +257,13 @@ export default createReactClass({ loginIncorrect: error.httpStatus === 401 || error.httpStatus === 403, }); }); - }, + }; - onUsernameChanged: function(username) { + onUsernameChanged = username => { this.setState({ username: username }); - }, + }; - onUsernameBlur: async function(username) { + onUsernameBlur = async username => { const doWellknownLookup = username[0] === "@"; this.setState({ username: username, @@ -314,19 +308,19 @@ export default createReactClass({ }); } } - }, + }; - onPhoneCountryChanged: function(phoneCountry) { + onPhoneCountryChanged = phoneCountry => { this.setState({ phoneCountry: phoneCountry }); - }, + }; - onPhoneNumberChanged: function(phoneNumber) { + onPhoneNumberChanged = phoneNumber => { this.setState({ phoneNumber: phoneNumber, }); - }, + }; - onPhoneNumberBlur: function(phoneNumber) { + onPhoneNumberBlur = phoneNumber => { // Validate the phone number entered if (!PHONE_NUMBER_REGEX.test(phoneNumber)) { this.setState({ @@ -339,15 +333,15 @@ export default createReactClass({ canTryLogin: true, }); } - }, + }; - onRegisterClick: function(ev) { + onRegisterClick = ev => { ev.preventDefault(); ev.stopPropagation(); this.props.onRegisterClick(); - }, + }; - onTryRegisterClick: function(ev) { + onTryRegisterClick = ev => { const step = this._getCurrentFlowStep(); if (step === 'm.login.sso' || step === 'm.login.cas') { // 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 this.onRegisterClick(ev); } - }, + }; - async onServerDetailsNextPhaseClick() { + onServerDetailsNextPhaseClick = () => { this.setState({ phase: PHASE_LOGIN, }); - }, + }; - onEditServerDetailsClick(ev) { + onEditServerDetailsClick = ev => { ev.preventDefault(); ev.stopPropagation(); this.setState({ phase: PHASE_SERVER_DETAILS, }); - }, + }; - _initLoginLogic: async function(hsUrl, isUrl) { + async _initLoginLogic(hsUrl, isUrl) { hsUrl = hsUrl || this.props.serverConfig.hsUrl; isUrl = isUrl || this.props.serverConfig.isUrl; @@ -465,9 +459,9 @@ export default createReactClass({ busy: false, }); }); - }, + } - _isSupportedFlow: function(flow) { + _isSupportedFlow(flow) { // 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. if (!this._stepRendererMap[flow.type]) { @@ -475,11 +469,11 @@ export default createReactClass({ return false; } return true; - }, + } - _getCurrentFlowStep: function() { + _getCurrentFlowStep() { return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null; - }, + } _errorTextFromError(err) { let errCode = err.errcode; @@ -526,7 +520,7 @@ export default createReactClass({ } return errorText; - }, + } renderServerComponent() { const ServerConfig = sdk.getComponent("auth.ServerConfig"); @@ -552,7 +546,7 @@ export default createReactClass({ delayTimeMs={250} {...serverDetailsProps} />; - }, + } renderLoginComponentForStep() { if (PHASES_ENABLED && this.state.phase !== PHASE_LOGIN) { @@ -572,9 +566,9 @@ export default createReactClass({ } return null; - }, + } - _renderPasswordStep: function() { + _renderPasswordStep() { const PasswordLogin = sdk.getComponent('auth.PasswordLogin'); let onEditServerDetailsClick = null; @@ -603,9 +597,9 @@ export default createReactClass({ busy={this.props.isSyncing || this.state.busyLoggingIn} /> ); - }, + } - _renderSsoStep: function(loginType) { + _renderSsoStep(loginType) { const SignInToText = sdk.getComponent('views.auth.SignInToText'); let onEditServerDetailsClick = null; @@ -634,9 +628,9 @@ export default createReactClass({ /> ); - }, + } - render: function() { + render() { const Loader = sdk.getComponent("elements.Spinner"); const InlineSpinner = sdk.getComponent("elements.InlineSpinner"); const AuthHeader = sdk.getComponent("auth.AuthHeader"); @@ -704,5 +698,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/structures/auth/PostRegistration.js b/src/components/structures/auth/PostRegistration.js index 687ab9a1956..aa36de65961 100644 --- a/src/components/structures/auth/PostRegistration.js +++ b/src/components/structures/auth/PostRegistration.js @@ -15,29 +15,24 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import AuthPage from "../../views/auth/AuthPage"; -export default createReactClass({ - displayName: 'PostRegistration', - - propTypes: { +export default class PostRegistration extends React.Component { + static propTypes = { onComplete: PropTypes.func.isRequired, - }, + }; - getInitialState: function() { - return { - avatarUrl: null, - errorString: null, - busy: false, - }; - }, + state = { + avatarUrl: null, + errorString: null, + busy: false, + }; - componentDidMount: function() { + componentDidMount() { // There is some assymetry between ChangeDisplayName and ChangeAvatar, // 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). @@ -55,9 +50,9 @@ export default createReactClass({ busy: false, }); }); - }, + } - render: function() { + render() { const ChangeDisplayName = sdk.getComponent('settings.ChangeDisplayName'); const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar'); const AuthHeader = sdk.getComponent('auth.AuthHeader'); @@ -78,5 +73,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 13e48f6287d..408d97382c9 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -19,7 +19,6 @@ limitations under the License. import Matrix from 'matrix-js-sdk'; import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import { _t, _td } from '../../../languageHandler'; @@ -43,10 +42,8 @@ const PHASE_REGISTRATION = 1; // Enable phases for registration const PHASES_ENABLED = true; -export default createReactClass({ - displayName: 'Registration', - - propTypes: { +export default class Registration extends React.Component { + static propTypes = { // Called when the user has logged in. Params: // - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken // - The user's password, if available and applicable (may be cached in memory @@ -65,12 +62,13 @@ export default createReactClass({ onLoginClick: PropTypes.func.isRequired, onServerConfigChange: PropTypes.func.isRequired, defaultDeviceDisplayName: PropTypes.string, - }, + }; - getInitialState: function() { - const serverType = ServerType.getTypeFromServerConfig(this.props.serverConfig); + constructor(props) { + super(props); - return { + const serverType = ServerType.getTypeFromServerConfig(this.props.serverConfig); + this.state = { busy: false, errorText: null, // 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. differentLoggedInUserId: null, }; - }, + } - componentDidMount: function() { + componentDidMount() { this._unmounted = false; this._replaceClient(); - }, + } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event UNSAFE_componentWillReceiveProps(newProps) { @@ -142,7 +140,7 @@ export default createReactClass({ phase: this.getDefaultPhaseForServerType(serverType), }); } - }, + } getDefaultPhaseForServerType(type) { switch (type) { @@ -155,9 +153,9 @@ export default createReactClass({ case ServerType.ADVANCED: return PHASE_SERVER_DETAILS; } - }, + } - onServerTypeChange(type) { + onServerTypeChange = type => { this.setState({ serverType: type, }); @@ -184,9 +182,9 @@ export default createReactClass({ this.setState({ phase: this.getDefaultPhaseForServerType(type), }); - }, + }; - _replaceClient: async function(serverConfig) { + async _replaceClient(serverConfig) { this.setState({ errorText: null, serverDeadError: null, @@ -286,18 +284,18 @@ export default createReactClass({ showGenericError(e); } } - }, + } - onFormSubmit: function(formVals) { + onFormSubmit = formVals => { this.setState({ errorText: "", busy: true, formVals: formVals, doingUIAuth: true, }); - }, + }; - _requestEmailToken: function(emailAddress, clientSecret, sendAttempt, sessionId) { + _requestEmailToken = (emailAddress, clientSecret, sendAttempt, sessionId) => { return this.state.matrixClient.requestRegisterEmailToken( emailAddress, clientSecret, @@ -309,9 +307,9 @@ export default createReactClass({ session_id: sessionId, }), ); - }, + } - _onUIAuthFinished: async function(success, response, extra) { + _onUIAuthFinished = async (success, response, extra) => { if (!success) { let msg = response.message || response.toString(); // can we give a better error message? @@ -395,9 +393,9 @@ export default createReactClass({ } this.setState(newState); - }, + }; - _setupPushers: function() { + _setupPushers() { if (!this.props.brand) { return Promise.resolve(); } @@ -418,15 +416,15 @@ export default createReactClass({ }, (error) => { console.error("Couldn't get pushers: " + error); }); - }, + } - onLoginClick: function(ev) { + onLoginClick = ev => { ev.preventDefault(); ev.stopPropagation(); this.props.onLoginClick(); - }, + }; - onGoToFormClicked(ev) { + onGoToFormClicked = ev => { ev.preventDefault(); ev.stopPropagation(); this._replaceClient(); @@ -435,23 +433,23 @@ export default createReactClass({ doingUIAuth: false, phase: PHASE_REGISTRATION, }); - }, + }; - async onServerDetailsNextPhaseClick() { + onServerDetailsNextPhaseClick = async () => { this.setState({ phase: PHASE_REGISTRATION, }); - }, + }; - onEditServerDetailsClick(ev) { + onEditServerDetailsClick = ev => { ev.preventDefault(); ev.stopPropagation(); this.setState({ phase: PHASE_SERVER_DETAILS, }); - }, + }; - _makeRegisterRequest: function(auth) { + _makeRegisterRequest = auth => { // 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 // 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 (inhibitLogin !== undefined && inhibitLogin !== null) registerParams.inhibit_login = inhibitLogin; return this.state.matrixClient.registerRequest(registerParams); - }, + }; - _getUIAuthInputs: function() { + _getUIAuthInputs() { return { emailAddress: this.state.formVals.email, phoneCountry: this.state.formVals.phoneCountry, phoneNumber: this.state.formVals.phoneNumber, }; - }, + } // 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 // this more generally?) - _onLoginClickWithCheck: async function(ev) { + _onLoginClickWithCheck = async ev => { ev.preventDefault(); 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 this.props.onLoginClick(); } - }, + }; renderServerComponent() { const ServerTypeSelector = sdk.getComponent("auth.ServerTypeSelector"); @@ -553,7 +551,7 @@ export default createReactClass({ /> {serverDetails} ; - }, + } renderRegisterComponent() { if (PHASES_ENABLED && this.state.phase !== PHASE_REGISTRATION) { @@ -608,9 +606,9 @@ export default createReactClass({ serverRequiresIdServer={this.state.serverRequiresIdServer} />; } - }, + } - render: function() { + render() { const AuthHeader = sdk.getComponent('auth.AuthHeader'); const AuthBody = sdk.getComponent("auth.AuthBody"); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); @@ -706,5 +704,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/auth/AuthFooter.js b/src/components/views/auth/AuthFooter.js index 13098007724..3de5a19350a 100644 --- a/src/components/views/auth/AuthFooter.js +++ b/src/components/views/auth/AuthFooter.js @@ -18,16 +18,13 @@ limitations under the License. import { _t } from '../../../languageHandler'; import React from 'react'; -import createReactClass from 'create-react-class'; -export default createReactClass({ - displayName: 'AuthFooter', - - render: function() { +export default class AuthFooter extends React.Component { + render() { return (
{ _t("powered by Matrix") }
); - }, -}); + } +} diff --git a/src/components/views/auth/AuthHeader.js b/src/components/views/auth/AuthHeader.js index 6e787ba77c8..57499e397cf 100644 --- a/src/components/views/auth/AuthHeader.js +++ b/src/components/views/auth/AuthHeader.js @@ -17,17 +17,14 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; -export default createReactClass({ - displayName: 'AuthHeader', - - propTypes: { +export default class AuthHeader extends React.Component { + static propTypes = { disableLanguageSelector: PropTypes.bool, - }, + }; - render: function() { + render() { const AuthHeaderLogo = sdk.getComponent('auth.AuthHeaderLogo'); const LanguageSelector = sdk.getComponent('views.auth.LanguageSelector'); @@ -37,5 +34,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/auth/CaptchaForm.js b/src/components/views/auth/CaptchaForm.js index e162603b01a..783d5196211 100644 --- a/src/components/views/auth/CaptchaForm.js +++ b/src/components/views/auth/CaptchaForm.js @@ -15,7 +15,6 @@ limitations under the License. */ import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; @@ -24,36 +23,31 @@ const DIV_ID = 'mx_recaptcha'; /** * A pure UI component which displays a captcha form. */ -export default createReactClass({ - displayName: 'CaptchaForm', - - propTypes: { +export default class CaptchaForm extends React.Component { + static propTypes = { sitePublicKey: PropTypes.string, // called with the captcha response onCaptchaResponse: PropTypes.func, - }, + }; - getDefaultProps: function() { - return { - onCaptchaResponse: () => {}, - }; - }, + static defaultProps = { + onCaptchaResponse: () => {}, + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this.state = { errorText: null, }; - }, - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this._captchaWidgetId = null; this._recaptchaContainer = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { // Just putting a script tag into the returned jsx doesn't work, annoyingly, // so we do this instead. if (global.grecaptcha) { @@ -68,13 +62,13 @@ export default createReactClass({ ); this._recaptchaContainer.current.appendChild(scriptTag); } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this._resetRecaptcha(); - }, + } - _renderRecaptcha: function(divId) { + _renderRecaptcha(divId) { if (!global.grecaptcha) { console.error("grecaptcha not loaded!"); throw new Error("Recaptcha did not load successfully"); @@ -93,15 +87,15 @@ export default createReactClass({ sitekey: publicKey, callback: this.props.onCaptchaResponse, }); - }, + } - _resetRecaptcha: function() { + _resetRecaptcha() { if (this._captchaWidgetId !== null) { global.grecaptcha.reset(this._captchaWidgetId); } - }, + } - _onCaptchaLoaded: function() { + _onCaptchaLoaded() { console.log("Loaded recaptcha script."); try { this._renderRecaptcha(DIV_ID); @@ -110,9 +104,9 @@ export default createReactClass({ errorText: e.toString(), }); } - }, + } - render: function() { + render() { let error = null; if (this.state.errorText) { error = ( @@ -131,5 +125,5 @@ export default createReactClass({ { error } ); - }, -}); + } +} diff --git a/src/components/views/auth/CustomServerDialog.js b/src/components/views/auth/CustomServerDialog.js index 7b2c8f88aae..138f8c4689f 100644 --- a/src/components/views/auth/CustomServerDialog.js +++ b/src/components/views/auth/CustomServerDialog.js @@ -16,14 +16,11 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; -export default createReactClass({ - displayName: 'CustomServerDialog', - - render: function() { +export default class CustomServerDialog extends React.Component { + render() { const brand = SdkConfig.get().brand; return (
@@ -46,5 +43,5 @@ export default createReactClass({
); - }, -}); + } +} diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index 7080eb36026..628c177d94d 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -17,7 +17,6 @@ limitations under the License. */ import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import url from 'url'; import classnames from 'classnames'; @@ -75,14 +74,10 @@ import AccessibleButton from "../elements/AccessibleButton"; export const DEFAULT_PHASE = 0; -export const PasswordAuthEntry = createReactClass({ - displayName: 'PasswordAuthEntry', +export class PasswordAuthEntry extends React.Component { + static LOGIN_TYPE = "m.login.password"; - statics: { - LOGIN_TYPE: "m.login.password", - }, - - propTypes: { + static propTypes = { matrixClient: PropTypes.object.isRequired, submitAuthDict: PropTypes.func.isRequired, errorText: PropTypes.string, @@ -90,19 +85,17 @@ export const PasswordAuthEntry = createReactClass({ // happen? busy: PropTypes.bool, onPhaseChange: PropTypes.func.isRequired, - }, + }; - componentDidMount: function() { + componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); - }, + } - getInitialState: function() { - return { - password: "", - }; - }, + state = { + password: "", + }; - _onSubmit: function(e) { + _onSubmit = e => { e.preventDefault(); if (this.props.busy) return; @@ -117,16 +110,16 @@ export const PasswordAuthEntry = createReactClass({ }, password: this.state.password, }); - }, + }; - _onPasswordFieldChange: function(ev) { + _onPasswordFieldChange = ev => { // enable the submit button iff the password is non-empty this.setState({ password: ev.target.value, }); - }, + }; - render: function() { + render() { const passwordBoxClass = classnames({ "error": this.props.errorText, }); @@ -176,36 +169,32 @@ export const PasswordAuthEntry = createReactClass({ { errorSection } ); - }, -}); - -export const RecaptchaAuthEntry = createReactClass({ - displayName: 'RecaptchaAuthEntry', + } +} - statics: { - LOGIN_TYPE: "m.login.recaptcha", - }, +export class RecaptchaAuthEntry extends React.Component { + static LOGIN_TYPE = "m.login.recaptcha"; - propTypes: { + static propTypes = { submitAuthDict: PropTypes.func.isRequired, stageParams: PropTypes.object.isRequired, errorText: PropTypes.string, busy: PropTypes.bool, onPhaseChange: PropTypes.func.isRequired, - }, + }; - componentDidMount: function() { + componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); - }, + } - _onCaptchaResponse: function(response) { + _onCaptchaResponse = response => { this.props.submitAuthDict({ type: RecaptchaAuthEntry.LOGIN_TYPE, response: response, }); - }, + }; - render: function() { + render() { if (this.props.busy) { const Loader = sdk.getComponent("elements.Spinner"); return ; @@ -241,31 +230,24 @@ export const RecaptchaAuthEntry = createReactClass({ { errorSection } ); - }, -}); - -export const TermsAuthEntry = createReactClass({ - displayName: 'TermsAuthEntry', + } +} - statics: { - LOGIN_TYPE: "m.login.terms", - }, +export class TermsAuthEntry extends React.Component { + static LOGIN_TYPE = "m.login.terms"; - propTypes: { + static propTypes = { submitAuthDict: PropTypes.func.isRequired, stageParams: PropTypes.object.isRequired, errorText: PropTypes.string, busy: PropTypes.bool, showContinue: PropTypes.bool, onPhaseChange: PropTypes.func.isRequired, - }, + }; - componentDidMount: function() { - this.props.onPhaseChange(DEFAULT_PHASE); - }, + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Move this to constructor - componentWillMount: function() { // example stageParams: // // { @@ -310,17 +292,22 @@ export const TermsAuthEntry = createReactClass({ pickedPolicies.push(langPolicy); } - this.setState({ - "toggledPolicies": initToggles, - "policies": pickedPolicies, - }); - }, + this.state = { + toggledPolicies: initToggles, + policies: pickedPolicies, + }; + } - tryContinue: function() { + + componentDidMount() { + this.props.onPhaseChange(DEFAULT_PHASE); + } + + tryContinue = () => { this._trySubmit(); - }, + }; - _togglePolicy: function(policyId) { + _togglePolicy(policyId) { const newToggles = {}; for (const policy of this.state.policies) { let checked = this.state.toggledPolicies[policy.id]; @@ -329,9 +316,9 @@ export const TermsAuthEntry = createReactClass({ newToggles[policy.id] = checked; } this.setState({"toggledPolicies": newToggles}); - }, + } - _trySubmit: function() { + _trySubmit = () => { let allChecked = true; for (const policy of this.state.policies) { const checked = this.state.toggledPolicies[policy.id]; @@ -340,9 +327,9 @@ export const TermsAuthEntry = createReactClass({ if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE}); else this.setState({errorText: _t("Please review and accept all of the homeserver's policies")}); - }, + }; - render: function() { + render() { if (this.props.busy) { const Loader = sdk.getComponent("elements.Spinner"); return ; @@ -387,17 +374,13 @@ export const TermsAuthEntry = createReactClass({ { submitButton } ); - }, -}); - -export const EmailIdentityAuthEntry = createReactClass({ - displayName: 'EmailIdentityAuthEntry', + } +} - statics: { - LOGIN_TYPE: "m.login.email.identity", - }, +export class EmailIdentityAuthEntry extends React.Component { + static LOGIN_TYPE = "m.login.email.identity"; - propTypes: { + static propTypes = { matrixClient: PropTypes.object.isRequired, submitAuthDict: PropTypes.func.isRequired, authSessionId: PropTypes.string.isRequired, @@ -407,13 +390,13 @@ export const EmailIdentityAuthEntry = createReactClass({ fail: PropTypes.func.isRequired, setEmailSid: PropTypes.func.isRequired, onPhaseChange: PropTypes.func.isRequired, - }, + }; - componentDidMount: function() { + componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); - }, + } - render: function() { + render() { // 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 // has clicked the validation link if the server takes a while to propagate @@ -434,17 +417,13 @@ export const EmailIdentityAuthEntry = createReactClass({ ); } - }, -}); - -export const MsisdnAuthEntry = createReactClass({ - displayName: 'MsisdnAuthEntry', + } +} - statics: { - LOGIN_TYPE: "m.login.msisdn", - }, +export class MsisdnAuthEntry extends React.Component { + static LOGIN_TYPE = "m.login.msisdn"; - propTypes: { + static propTypes = { inputs: PropTypes.shape({ phoneCountry: PropTypes.string, phoneNumber: PropTypes.string, @@ -454,16 +433,14 @@ export const MsisdnAuthEntry = createReactClass({ submitAuthDict: PropTypes.func.isRequired, matrixClient: PropTypes.object, onPhaseChange: PropTypes.func.isRequired, - }, + }; - getInitialState: function() { - return { - token: '', - requestingToken: false, - }; - }, + state = { + token: '', + requestingToken: false, + }; - componentDidMount: function() { + componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); this._submitUrl = null; @@ -477,12 +454,12 @@ export const MsisdnAuthEntry = createReactClass({ }).finally(() => { this.setState({requestingToken: false}); }); - }, + } /* * Requests a verification token by SMS. */ - _requestMsisdnToken: function() { + _requestMsisdnToken() { return this.props.matrixClient.requestRegisterMsisdnToken( this.props.inputs.phoneCountry, this.props.inputs.phoneNumber, @@ -493,15 +470,15 @@ export const MsisdnAuthEntry = createReactClass({ this._sid = result.sid; this._msisdn = result.msisdn; }); - }, + } - _onTokenChange: function(e) { + _onTokenChange = e => { this.setState({ token: e.target.value, }); - }, + }; - _onFormSubmit: async function(e) { + _onFormSubmit = async e => { e.preventDefault(); if (this.state.token == '') return; @@ -552,9 +529,9 @@ export const MsisdnAuthEntry = createReactClass({ this.props.fail(e); console.log("Failed to submit msisdn token"); } - }, + }; - render: function() { + render() { if (this.state.requestingToken) { const Loader = sdk.getComponent("elements.Spinner"); return ; @@ -598,8 +575,8 @@ export const MsisdnAuthEntry = createReactClass({ ); } - }, -}); + } +} export class SSOAuthEntry extends React.Component { static propTypes = { @@ -686,46 +663,46 @@ export class SSOAuthEntry extends React.Component { } } -export const FallbackAuthEntry = createReactClass({ - displayName: 'FallbackAuthEntry', - - propTypes: { +export class FallbackAuthEntry extends React.Component { + static propTypes = { matrixClient: PropTypes.object.isRequired, authSessionId: PropTypes.string.isRequired, loginType: PropTypes.string.isRequired, submitAuthDict: PropTypes.func.isRequired, errorText: PropTypes.string, onPhaseChange: PropTypes.func.isRequired, - }, + }; - componentDidMount: function() { - this.props.onPhaseChange(DEFAULT_PHASE); - }, + constructor(props) { + 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 // the popup if we open it immediately. this._popupWindow = null; window.addEventListener("message", this._onReceiveMessage); this._fallbackButton = createRef(); - }, + } - componentWillUnmount: function() { + + componentDidMount() { + this.props.onPhaseChange(DEFAULT_PHASE); + } + + componentWillUnmount() { window.removeEventListener("message", this._onReceiveMessage); if (this._popupWindow) { this._popupWindow.close(); } - }, + } - focus: function() { + focus = () => { if (this._fallbackButton.current) { this._fallbackButton.current.focus(); } - }, + }; - _onShowFallbackClick: function(e) { + _onShowFallbackClick = e => { e.preventDefault(); e.stopPropagation(); @@ -735,18 +712,18 @@ export const FallbackAuthEntry = createReactClass({ ); this._popupWindow = window.open(url); this._popupWindow.opener = null; - }, + }; - _onReceiveMessage: function(event) { + _onReceiveMessage = event => { if ( event.data === "authDone" && event.origin === this.props.matrixClient.getHomeserverUrl() ) { this.props.submitAuthDict({}); } - }, + }; - render: function() { + render() { let errorSection; if (this.props.errorText) { errorSection = ( @@ -761,8 +738,8 @@ export const FallbackAuthEntry = createReactClass({ {errorSection} ); - }, -}); + } +} const AuthEntryComponents = [ PasswordAuthEntry, diff --git a/src/components/views/auth/RegistrationForm.js b/src/components/views/auth/RegistrationForm.js index 17c65fa94e0..bd10c829e33 100644 --- a/src/components/views/auth/RegistrationForm.js +++ b/src/components/views/auth/RegistrationForm.js @@ -18,7 +18,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; 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. */ -export default createReactClass({ - displayName: 'RegistrationForm', - - propTypes: { +export default class RegistrationForm extends React.Component { + static propTypes = { // Values pre-filled in the input boxes when the component loads defaultEmail: PropTypes.string, defaultPhoneCountry: PropTypes.string, @@ -58,17 +55,17 @@ export default createReactClass({ serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, canSubmit: PropTypes.bool, serverRequiresIdServer: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - onValidationChange: console.error, - canSubmit: true, - }; - }, + static defaultProps = { + onValidationChange: console.error, + canSubmit: true, + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this.state = { // Field error codes by field ID fieldValid: {}, // The ISO2 country code selected in the phone number entry @@ -80,9 +77,9 @@ export default createReactClass({ passwordConfirm: this.props.defaultPassword || "", passwordComplexity: null, }; - }, + } - onSubmit: async function(ev) { + onSubmit = async ev => { ev.preventDefault(); if (!this.props.canSubmit) return; @@ -118,7 +115,7 @@ export default createReactClass({ title: _t("Warning!"), description: desc, button: _t("Continue"), - onFinished: function(confirmed) { + onFinished(confirmed) { if (confirmed) { self._doSubmit(ev); } @@ -127,9 +124,9 @@ export default createReactClass({ } else { self._doSubmit(ev); } - }, + }; - _doSubmit: function(ev) { + _doSubmit(ev) { const email = this.state.email.trim(); const promise = this.props.onRegisterClick({ username: this.state.username.trim(), @@ -145,7 +142,7 @@ export default createReactClass({ ev.target.disabled = false; }); } - }, + } async verifyFieldsBeforeSubmit() { // Blur the active element if any, so we first run its blur validation, @@ -196,12 +193,12 @@ export default createReactClass({ invalidField.focus(); invalidField.validate({ allowEmpty: false, focused: true }); return false; - }, + } /** * @returns {boolean} true if all fields were valid last time they were validated. */ - allFieldsValid: function() { + allFieldsValid() { const keys = Object.keys(this.state.fieldValid); for (let i = 0; i < keys.length; ++i) { if (!this.state.fieldValid[keys[i]]) { @@ -209,7 +206,7 @@ export default createReactClass({ } } return true; - }, + } findFirstInvalidField(fieldIDs) { for (const fieldID of fieldIDs) { @@ -218,34 +215,34 @@ export default createReactClass({ } } return null; - }, + } - markFieldValid: function(fieldID, valid) { + markFieldValid(fieldID, valid) { const { fieldValid } = this.state; fieldValid[fieldID] = valid; this.setState({ fieldValid, }); - }, + } - onEmailChange(ev) { + onEmailChange = ev => { this.setState({ email: ev.target.value, }); - }, + }; - async onEmailValidate(fieldState) { - const result = await this.validateEmailRules(fieldState); + onEmailValidate = async fieldState => { + const result = await RegistrationForm.validateEmailRules(fieldState); this.markFieldValid(FIELD_EMAIL, result.valid); return result; - }, + }; - validateEmailRules: withValidation({ + static validateEmailRules = withValidation({ description: () => _t("Use an email address to recover your account"), rules: [ { key: "required", - test: function({ value, allowEmpty }) { + test({ value, allowEmpty }) { return allowEmpty || !this._authStepIsRequired('m.login.email.identity') || !!value; }, 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"), }, ], - }), + }); - onPasswordChange(ev) { + onPasswordChange = ev => { this.setState({ password: ev.target.value, }); - }, + }; - onPasswordValidate(result) { + onPasswordValidate = result => { this.markFieldValid(FIELD_PASSWORD, result.valid); - }, + }; - onPasswordConfirmChange(ev) { + onPasswordConfirmChange = ev => { this.setState({ passwordConfirm: ev.target.value, }); - }, + }; - async onPasswordConfirmValidate(fieldState) { - const result = await this.validatePasswordConfirmRules(fieldState); + onPasswordConfirmValidate = async fieldState => { + const result = await RegistrationForm.validatePasswordConfirmRules(fieldState); this.markFieldValid(FIELD_PASSWORD_CONFIRM, result.valid); return result; - }, + }; - validatePasswordConfirmRules: withValidation({ + static validatePasswordConfirmRules = withValidation({ rules: [ { key: "required", @@ -289,39 +286,39 @@ export default createReactClass({ }, { key: "match", - test: function({ value }) { + test({ value }) { return !value || value === this.state.password; }, invalid: () => _t("Passwords don't match"), }, ], - }), + }); - onPhoneCountryChange(newVal) { + onPhoneCountryChange = newVal => { this.setState({ phoneCountry: newVal.iso2, phonePrefix: newVal.prefix, }); - }, + }; - onPhoneNumberChange(ev) { + onPhoneNumberChange = ev => { this.setState({ phoneNumber: ev.target.value, }); - }, + }; - async onPhoneNumberValidate(fieldState) { - const result = await this.validatePhoneNumberRules(fieldState); + onPhoneNumberValidate = async fieldState => { + const result = await RegistrationForm.validatePhoneNumberRules(fieldState); this.markFieldValid(FIELD_PHONE_NUMBER, result.valid); return result; - }, + }; - validatePhoneNumberRules: withValidation({ + static validatePhoneNumberRules = withValidation({ description: () => _t("Other users can invite you to rooms using your contact details"), rules: [ { key: "required", - test: function({ value, allowEmpty }) { + test({ value, allowEmpty }) { return allowEmpty || !this._authStepIsRequired('m.login.msisdn') || !!value; }, 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"), }, ], - }), + }); - onUsernameChange(ev) { + onUsernameChange = ev => { this.setState({ username: ev.target.value, }); - }, + }; - async onUsernameValidate(fieldState) { - const result = await this.validateUsernameRules(fieldState); + onUsernameValidate = async fieldState => { + const result = await RegistrationForm.validateUsernameRules(fieldState); this.markFieldValid(FIELD_USERNAME, result.valid); return result; - }, + }; - validateUsernameRules: withValidation({ + static validateUsernameRules = withValidation({ description: () => _t("Use lowercase letters, numbers, dashes and underscores only"), rules: [ { @@ -360,7 +357,7 @@ export default createReactClass({ invalid: () => _t("Some characters not allowed"), }, ], - }), + }); /** * A step is required if all flows include that step. @@ -372,7 +369,7 @@ export default createReactClass({ return this.props.flows.every((flow) => { return flow.stages.includes(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 flow.stages.includes(step); }); - }, + } _showEmail() { const haveIs = Boolean(this.props.serverConfig.isUrl); @@ -395,7 +392,7 @@ export default createReactClass({ return false; } return true; - }, + } _showPhoneNumber() { const threePidLogin = !SdkConfig.get().disable_3pid_login; @@ -408,7 +405,7 @@ export default createReactClass({ return false; } return true; - }, + } renderEmail() { if (!this._showEmail()) { @@ -426,7 +423,7 @@ export default createReactClass({ onChange={this.onEmailChange} onValidate={this.onEmailValidate} />; - }, + } renderPassword() { return ; - }, + } renderPasswordConfirm() { const Field = sdk.getComponent('elements.Field'); @@ -451,7 +448,7 @@ export default createReactClass({ onChange={this.onPasswordConfirmChange} onValidate={this.onPasswordConfirmValidate} />; - }, + } renderPhoneNumber() { if (!this._showPhoneNumber()) { @@ -477,7 +474,7 @@ export default createReactClass({ onChange={this.onPhoneNumberChange} onValidate={this.onPhoneNumberValidate} />; - }, + } renderUsername() { const Field = sdk.getComponent('elements.Field'); @@ -491,9 +488,9 @@ export default createReactClass({ onChange={this.onUsernameChange} onValidate={this.onUsernameValidate} />; - }, + } - render: function() { + render() { let yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', { serverName: this.props.serverConfig.hsName, }); @@ -578,5 +575,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 6fa54058a0d..d760c8defad 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -19,7 +19,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {EventStatus} from 'matrix-js-sdk'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; @@ -37,10 +36,8 @@ function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; } -export default createReactClass({ - displayName: 'MessageContextMenu', - - propTypes: { +export default class MessageContextMenu extends React.Component { + static propTypes = { /* the MatrixEvent associated with the context menu */ mxEvent: PropTypes.object.isRequired, @@ -52,28 +49,26 @@ export default createReactClass({ /* callback called when the menu is dismissed */ onFinished: PropTypes.func, - }, + }; - getInitialState: function() { - return { - canRedact: false, - canPin: false, - }; - }, + state = { + canRedact: false, + canPin: false, + }; - componentDidMount: function() { + componentDidMount() { MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions); this._checkPermissions(); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { const cli = MatrixClientPeg.get(); if (cli) { cli.removeListener('RoomMember.powerLevel', this._checkPermissions); } - }, + } - _checkPermissions: function() { + _checkPermissions = () => { const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.mxEvent.getRoomId()); @@ -84,47 +79,47 @@ export default createReactClass({ if (!SettingsStore.getValue("feature_pinning")) canPin = false; this.setState({canRedact, canPin}); - }, + }; - _isPinned: function() { + _isPinned() { const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', ''); if (!pinnedEvent) return false; const content = pinnedEvent.getContent(); return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId()); - }, + } - onResendClick: function() { + onResendClick = () => { Resend.resend(this.props.mxEvent); this.closeMenu(); - }, + }; - onResendEditClick: function() { + onResendEditClick = () => { Resend.resend(this.props.mxEvent.replacingEvent()); this.closeMenu(); - }, + }; - onResendRedactionClick: function() { + onResendRedactionClick = () => { Resend.resend(this.props.mxEvent.localRedactionEvent()); this.closeMenu(); - }, + }; - onResendReactionsClick: function() { + onResendReactionsClick = () => { for (const reaction of this._getUnsentReactions()) { Resend.resend(reaction); } this.closeMenu(); - }, + }; - onReportEventClick: function() { + onReportEventClick = () => { const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog"); Modal.createTrackedDialog('Report Event', '', ReportEventDialog, { mxEvent: this.props.mxEvent, }, 'mx_Dialog_reportEvent'); this.closeMenu(); - }, + }; - onViewSourceClick: function() { + onViewSourceClick = () => { const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent; const ViewSource = sdk.getComponent('structures.ViewSource'); Modal.createTrackedDialog('View Event Source', '', ViewSource, { @@ -133,9 +128,9 @@ export default createReactClass({ content: ev.event, }, 'mx_Dialog_viewsource'); this.closeMenu(); - }, + }; - onViewClearSourceClick: function() { + onViewClearSourceClick = () => { const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent; const ViewSource = sdk.getComponent('structures.ViewSource'); Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, { @@ -145,9 +140,9 @@ export default createReactClass({ content: ev._clearEvent, }, 'mx_Dialog_viewsource'); this.closeMenu(); - }, + }; - onRedactClick: function() { + onRedactClick = () => { const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog"); Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, { onFinished: async (proceed) => { @@ -176,9 +171,9 @@ export default createReactClass({ }, }, 'mx_Dialog_confirmredact'); this.closeMenu(); - }, + }; - onCancelSendClick: function() { + onCancelSendClick = () => { const mxEvent = this.props.mxEvent; const editEvent = mxEvent.replacingEvent(); const redactEvent = mxEvent.localRedactionEvent(); @@ -199,17 +194,17 @@ export default createReactClass({ Resend.removeFromQueue(this.props.mxEvent); } this.closeMenu(); - }, + }; - onForwardClick: function() { + onForwardClick = () => { dis.dispatch({ action: 'forward_event', event: this.props.mxEvent, }); this.closeMenu(); - }, + }; - onPinClick: function() { + onPinClick = () => { MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '') .catch((e) => { // 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}, ''); }); this.closeMenu(); - }, + }; - closeMenu: function() { + closeMenu = () => { if (this.props.onFinished) this.props.onFinished(); - }, + }; - onUnhidePreviewClick: function() { + onUnhidePreviewClick = () => { if (this.props.eventTileOps) { this.props.eventTileOps.unhideWidget(); } this.closeMenu(); - }, + }; - onQuoteClick: function() { + onQuoteClick = () => { dis.dispatch({ action: 'quote', event: this.props.mxEvent, }); this.closeMenu(); - }, + }; - onPermalinkClick: function(e: Event) { + onPermalinkClick = (e: Event) => { e.preventDefault(); const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); Modal.createTrackedDialog('share room message dialog', '', ShareDialog, { @@ -259,12 +254,12 @@ export default createReactClass({ permalinkCreator: this.props.permalinkCreator, }); this.closeMenu(); - }, + }; - onCollapseReplyThreadClick: function() { + onCollapseReplyThreadClick = () => { this.props.collapseReplyThread(); this.closeMenu(); - }, + }; _getReactions(filter) { const cli = MatrixClientPeg.get(); @@ -277,17 +272,17 @@ export default createReactClass({ relation.event_id === eventId && filter(e); }); - }, + } _getPendingReactions() { return this._getReactions(e => canCancel(e.status)); - }, + } _getUnsentReactions() { return this._getReactions(e => e.status === EventStatus.NOT_SENT); - }, + } - render: function() { + render() { const cli = MatrixClientPeg.get(); const me = cli.getUserId(); const mxEvent = this.props.mxEvent; @@ -489,5 +484,5 @@ export default createReactClass({ { reportEventButton } ); - }, -}); + } +} diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js index 8ddd89dc655..2cd09874b21 100644 --- a/src/components/views/dialogs/AddressPickerDialog.js +++ b/src/components/views/dialogs/AddressPickerDialog.js @@ -19,7 +19,6 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { _t, _td } from '../../../languageHandler'; import * as sdk from '../../../index'; @@ -45,10 +44,8 @@ const addressTypeName = { }; -export default createReactClass({ - displayName: "AddressPickerDialog", - - propTypes: { +export default class AddressPickerDialog extends React.Component { + static propTypes = { title: PropTypes.string.isRequired, description: PropTypes.node, // 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 // applicable when pickerType is `user`. Default: false. includeSelf: PropTypes.bool, - }, - - getDefaultProps: function() { - return { - value: "", - focus: true, - validAddressTypes: addressTypes, - pickerType: 'user', - includeSelf: false, - }; - }, + }; + + static defaultProps = { + value: "", + focus: true, + validAddressTypes: addressTypes, + pickerType: 'user', + includeSelf: false, + }; + + constructor(props) { + super(props); + + this._textinput = createRef(); - getInitialState: function() { 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 if (!MatrixClientPeg.get().getIdentityServerUrl() && validAddressTypes.includes("email")) { validAddressTypes = validAddressTypes.filter(type => type !== "email"); } - return { + this.state = { // Whether to show an error message because of an invalid address invalidAddressError: false, // 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. validAddressTypes, }; - }, + } - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { - this._textinput = createRef(); - }, - - componentDidMount: function() { + componentDidMount() { if (this.props.focus) { // Set the cursor at the end of the text input this._textinput.current.value = this.props.value; } - }, + } getPlaceholder() { const { placeholder } = this.props; @@ -127,9 +121,9 @@ export default createReactClass({ } // Otherwise it's a function, as checked by prop types. return placeholder(this.state.validAddressTypes); - }, + } - onButtonClick: function() { + onButtonClick = () => { let selectedList = this.state.selectedList.slice(); // 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 @@ -138,13 +132,13 @@ export default createReactClass({ if (selectedList === null) return; } this.props.onFinished(true, selectedList); - }, + }; - onCancel: function() { + onCancel = () => { this.props.onFinished(false); - }, + }; - onKeyDown: function(e) { + onKeyDown = e => { const textInput = this._textinput.current ? this._textinput.current.value : undefined; if (e.key === Key.ESCAPE) { @@ -181,9 +175,9 @@ export default createReactClass({ e.preventDefault(); this._addAddressesToList([textInput]); } - }, + }; - onQueryChanged: function(ev) { + onQueryChanged = ev => { const query = ev.target.value; if (this.queryChangedDebouncer) { clearTimeout(this.queryChangedDebouncer); @@ -216,28 +210,24 @@ export default createReactClass({ searchError: null, }); } - }, + }; - onDismissed: function(index) { - return () => { - const selectedList = this.state.selectedList.slice(); - selectedList.splice(index, 1); - this.setState({ - selectedList, - suggestedList: [], - query: "", - }); - if (this._cancelThreepidLookup) this._cancelThreepidLookup(); - }; - }, + onDismissed = index => () => { + const selectedList = this.state.selectedList.slice(); + selectedList.splice(index, 1); + this.setState({ + selectedList, + suggestedList: [], + query: "", + }); + if (this._cancelThreepidLookup) this._cancelThreepidLookup(); + }; - onClick: function(index) { - return () => { - this.onSelected(index); - }; - }, + onClick = index => () => { + this.onSelected(index); + }; - onSelected: function(index) { + onSelected = index => { const selectedList = this.state.selectedList.slice(); selectedList.push(this._getFilteredSuggestions()[index]); this.setState({ @@ -246,9 +236,9 @@ export default createReactClass({ query: "", }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); - }, + }; - _doNaiveGroupSearch: function(query) { + _doNaiveGroupSearch(query) { const lowerCaseQuery = query.toLowerCase(); this.setState({ busy: true, @@ -280,9 +270,9 @@ export default createReactClass({ busy: false, }); }); - }, + } - _doNaiveGroupRoomSearch: function(query) { + _doNaiveGroupRoomSearch(query) { const lowerCaseQuery = query.toLowerCase(); const results = []; GroupStore.getGroupRooms(this.props.groupId).forEach((r) => { @@ -302,9 +292,9 @@ export default createReactClass({ this.setState({ busy: false, }); - }, + } - _doRoomSearch: function(query) { + _doRoomSearch(query) { const lowerCaseQuery = query.toLowerCase(); const rooms = MatrixClientPeg.get().getRooms(); const results = []; @@ -359,9 +349,9 @@ export default createReactClass({ this.setState({ busy: false, }); - }, + } - _doUserDirectorySearch: function(query) { + _doUserDirectorySearch(query) { this.setState({ busy: true, query, @@ -393,9 +383,9 @@ export default createReactClass({ busy: false, }); }); - }, + } - _doLocalSearch: function(query) { + _doLocalSearch(query) { this.setState({ query, searchError: null, @@ -417,9 +407,9 @@ export default createReactClass({ }); }); this._processResults(results, query); - }, + } - _processResults: function(results, query) { + _processResults(results, query) { const suggestedList = []; results.forEach((result) => { if (result.room_id) { @@ -485,9 +475,9 @@ export default createReactClass({ }, () => { if (this.addressSelector) this.addressSelector.moveSelectionTop(); }); - }, + } - _addAddressesToList: function(addressTexts) { + _addAddressesToList(addressTexts) { const selectedList = this.state.selectedList.slice(); let hasError = false; @@ -529,9 +519,9 @@ export default createReactClass({ }); if (this._cancelThreepidLookup) this._cancelThreepidLookup(); return hasError ? null : selectedList; - }, + } - _lookupThreepid: async function(medium, address) { + async _lookupThreepid(medium, address) { let cancelled = false; // 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 @@ -577,9 +567,9 @@ export default createReactClass({ searchError: _t('Something went wrong!'), }); } - }, + } - _getFilteredSuggestions: function() { + _getFilteredSuggestions() { // map addressType => set of addresses to avoid O(n*m) operation const selectedAddresses = {}; this.state.selectedList.forEach(({address, addressType}) => { @@ -591,17 +581,17 @@ export default createReactClass({ return this.state.suggestedList.filter(({address, addressType}) => { return !(selectedAddresses[addressType] && selectedAddresses[addressType].has(address)); }); - }, + } - _onPaste: function(e) { + _onPaste = e => { // Prevent the text being pasted into the textarea e.preventDefault(); const text = e.clipboardData.getData("text"); // Process it as a list of addresses to add instead this._addAddressesToList(text.split(/[\s,]+/)); - }, + }; - onUseDefaultIdentityServerClick(e) { + onUseDefaultIdentityServerClick = e => { e.preventDefault(); // Update the IS in account data. Actually using it may trigger terms. @@ -612,15 +602,15 @@ export default createReactClass({ const { validAddressTypes } = this.state; validAddressTypes.push('email'); this.setState({ validAddressTypes }); - }, + }; - onManageSettingsClick(e) { + onManageSettingsClick = e => { e.preventDefault(); dis.fire(Action.ViewUserSettings); this.onCancel(); - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const AddressSelector = sdk.getComponent("elements.AddressSelector"); @@ -738,5 +728,5 @@ export default createReactClass({ onCancel={this.onCancel} /> ); - }, -}); + } +} diff --git a/src/components/views/dialogs/AskInviteAnywayDialog.js b/src/components/views/dialogs/AskInviteAnywayDialog.js index 7a12d2bd203..c69400977a3 100644 --- a/src/components/views/dialogs/AskInviteAnywayDialog.js +++ b/src/components/views/dialogs/AskInviteAnywayDialog.js @@ -16,37 +16,36 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; import {SettingLevel} from "../../../settings/SettingLevel"; -export default createReactClass({ - propTypes: { +export default class AskInviteAnywayDialog extends React.Component { + static propTypes = { unknownProfileUsers: PropTypes.array.isRequired, // [ {userId, errorText}... ] onInviteAnyways: PropTypes.func.isRequired, onGiveUp: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired, - }, + }; - _onInviteClicked: function() { + _onInviteClicked = () => { this.props.onInviteAnyways(); this.props.onFinished(true); - }, + }; - _onInviteNeverWarnClicked: function() { + _onInviteNeverWarnClicked = () => { SettingsStore.setValue("promptBeforeInviteUnknownUsers", null, SettingLevel.ACCOUNT, false); this.props.onInviteAnyways(); this.props.onFinished(true); - }, + }; - _onGiveUpClicked: function() { + _onGiveUpClicked = () => { this.props.onGiveUp(); this.props.onFinished(false); - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const errorList = this.props.unknownProfileUsers @@ -78,5 +77,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index 353298032c6..55760e54fc7 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import FocusLock from 'react-focus-lock'; import PropTypes from 'prop-types'; 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 * dialog on escape. */ -export default createReactClass({ - displayName: 'BaseDialog', - - propTypes: { +export default class BaseDialog extends React.Component { + static propTypes = { // onFinished callback to call when Escape is pressed // Take a boolean which is true if the dialog was dismissed // with a positive / confirm action or false if it was @@ -81,21 +78,20 @@ export default createReactClass({ PropTypes.object, PropTypes.arrayOf(PropTypes.string), ]), - }, + }; + + static defaultProps = { + hasCancel: true, + fixedWidth: true, + }; - getDefaultProps: function() { - return { - hasCancel: true, - fixedWidth: true, - }; - }, + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Move this to constructor - UNSAFE_componentWillMount() { this._matrixClient = MatrixClientPeg.get(); - }, + } - _onKeyDown: function(e) { + _onKeyDown = (e) => { if (this.props.onKeyDown) { this.props.onKeyDown(e); } @@ -104,13 +100,13 @@ export default createReactClass({ e.preventDefault(); this.props.onFinished(false); } - }, + }; - _onCancelClick: function(e) { + _onCancelClick = (e) => { this.props.onFinished(false); - }, + }; - render: function() { + render() { let cancelButton; if (this.props.hasCancel) { cancelButton = ( @@ -161,5 +157,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/dialogs/ConfirmRedactDialog.js b/src/components/views/dialogs/ConfirmRedactDialog.js index 71139155eca..3106df1d5be 100644 --- a/src/components/views/dialogs/ConfirmRedactDialog.js +++ b/src/components/views/dialogs/ConfirmRedactDialog.js @@ -15,17 +15,14 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; /* * A dialog for confirming a redaction. */ -export default createReactClass({ - displayName: 'ConfirmRedactDialog', - - render: function() { +export default class ConfirmRedactDialog extends React.Component { + render() { const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog'); return ( ); - }, -}); + } +} diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.js b/src/components/views/dialogs/ConfirmUserActionDialog.js index 2495c463277..44f57f047e9 100644 --- a/src/components/views/dialogs/ConfirmUserActionDialog.js +++ b/src/components/views/dialogs/ConfirmUserActionDialog.js @@ -15,7 +15,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import { MatrixClient } from 'matrix-js-sdk'; import * as sdk from '../../../index'; @@ -30,9 +29,8 @@ import { GroupMemberType } from '../../../groups'; * to make it obvious what is going to happen. * Also tweaks the style for 'dangerous' actions (albeit only with colour) */ -export default createReactClass({ - displayName: 'ConfirmUserActionDialog', - propTypes: { +export default class ConfirmUserActionDialog extends React.Component { + static propTypes = { // matrix-js-sdk (room) member object. Supply either this or 'groupMember' member: PropTypes.object, // group member object. Supply either this or 'member' @@ -48,35 +46,36 @@ export default createReactClass({ askReason: PropTypes.bool, danger: PropTypes.bool, onFinished: PropTypes.func.isRequired, - }, + }; - getDefaultProps: () => ({ + static defaultProps = { danger: false, askReason: false, - }), + }; + + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Move this to constructor - UNSAFE_componentWillMount: function() { this._reasonField = null; - }, + } - onOk: function() { + onOk = () => { let reason; if (this._reasonField) { reason = this._reasonField.value; } this.props.onFinished(true, reason); - }, + }; - onCancel: function() { + onCancel = () => { this.props.onFinished(false); - }, + }; - _collectReasonField: function(e) { + _collectReasonField = e => { this._reasonField = e; - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar"); @@ -134,5 +133,5 @@ export default createReactClass({ onCancel={this.onCancel} /> ); - }, -}); + } +} diff --git a/src/components/views/dialogs/CreateGroupDialog.js b/src/components/views/dialogs/CreateGroupDialog.js index 10285ccee0f..6636153c985 100644 --- a/src/components/views/dialogs/CreateGroupDialog.js +++ b/src/components/views/dialogs/CreateGroupDialog.js @@ -15,46 +15,42 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; -export default createReactClass({ - displayName: 'CreateGroupDialog', - propTypes: { +export default class CreateGroupDialog extends React.Component { + static propTypes = { onFinished: PropTypes.func.isRequired, - }, - - getInitialState: function() { - return { - groupName: '', - groupId: '', - groupError: null, - creating: false, - createError: null, - }; - }, + }; + + state = { + groupName: '', + groupId: '', + groupError: null, + creating: false, + createError: null, + }; - _onGroupNameChange: function(e) { + _onGroupNameChange = e => { this.setState({ groupName: e.target.value, }); - }, + }; - _onGroupIdChange: function(e) { + _onGroupIdChange = e => { this.setState({ groupId: e.target.value, }); - }, + }; - _onGroupIdBlur: function(e) { + _onGroupIdBlur = e => { this._checkGroupId(); - }, + }; - _checkGroupId: function(e) { + _checkGroupId(e) { let error = null; if (!this.state.groupId) { error = _t("Community IDs cannot be empty."); @@ -67,9 +63,9 @@ export default createReactClass({ createError: null, }); return error; - }, + } - _onFormSubmit: function(e) { + _onFormSubmit = e => { e.preventDefault(); if (this._checkGroupId()) return; @@ -94,13 +90,13 @@ export default createReactClass({ }).finally(() => { this.setState({creating: false}); }); - }, + }; - _onCancel: function() { + _onCancel = () => { this.props.onFinished(false); - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const Spinner = sdk.getComponent('elements.Spinner'); @@ -171,5 +167,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 4890626527a..0fc8cf50b23 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; @@ -28,16 +27,17 @@ import {privateShouldBeEncrypted} from "../../../createRoom"; import TagOrderStore from "../../../stores/TagOrderStore"; import GroupStore from "../../../stores/GroupStore"; -export default createReactClass({ - displayName: 'CreateRoomDialog', - propTypes: { +export default class CreateRoomDialog extends React.Component { + static propTypes = { onFinished: PropTypes.func.isRequired, defaultPublic: PropTypes.bool, - }, + }; + + constructor(props) { + super(props); - getInitialState() { const config = SdkConfig.get(); - return { + this.state = { isPublic: this.props.defaultPublic || false, isEncrypted: privateShouldBeEncrypted(), name: "", @@ -47,7 +47,7 @@ export default createReactClass({ noFederate: config.default_federate === false, nameIsValid: false, }; - }, + } _roomCreateOptions() { const opts = {}; @@ -77,27 +77,27 @@ export default createReactClass({ } return opts; - }, + } componentDidMount() { this._detailsRef.addEventListener("toggle", this.onDetailsToggled); // move focus to first field when showing dialog this._nameFieldRef.focus(); - }, + } componentWillUnmount() { this._detailsRef.removeEventListener("toggle", this.onDetailsToggled); - }, + } - _onKeyDown: function(event) { + _onKeyDown = event => { if (event.key === Key.ENTER) { this.onOk(); event.preventDefault(); event.stopPropagation(); } - }, + }; - onOk: async function() { + onOk = async () => { const activeElement = document.activeElement; if (activeElement) { activeElement.blur(); @@ -123,51 +123,51 @@ export default createReactClass({ field.validate({ allowEmpty: false, focused: true }); } } - }, + }; - onCancel: function() { + onCancel = () => { this.props.onFinished(false); - }, + }; - onNameChange(ev) { + onNameChange = ev => { this.setState({name: ev.target.value}); - }, + }; - onTopicChange(ev) { + onTopicChange = ev => { this.setState({topic: ev.target.value}); - }, + }; - onPublicChange(isPublic) { + onPublicChange = isPublic => { this.setState({isPublic}); - }, + }; - onEncryptedChange(isEncrypted) { + onEncryptedChange = isEncrypted => { this.setState({isEncrypted}); - }, + }; - onAliasChange(alias) { + onAliasChange = alias => { this.setState({alias}); - }, + }; - onDetailsToggled(ev) { + onDetailsToggled = ev => { this.setState({detailsOpen: ev.target.open}); - }, + }; - onNoFederateChange(noFederate) { + onNoFederateChange = noFederate => { this.setState({noFederate}); - }, + }; - collectDetailsRef(ref) { + collectDetailsRef = ref => { this._detailsRef = ref; - }, + }; - async onNameValidate(fieldState) { - const result = await this._validateRoomName(fieldState); + onNameValidate = async fieldState => { + const result = await CreateRoomDialog._validateRoomName(fieldState); this.setState({nameIsValid: result.valid}); return result; - }, + }; - _validateRoomName: withValidation({ + static _validateRoomName = withValidation({ rules: [ { key: "required", @@ -175,9 +175,9 @@ export default createReactClass({ invalid: () => _t("Please enter a name for the room"), }, ], - }), + }); - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const Field = sdk.getComponent('views.elements.Field'); @@ -275,5 +275,5 @@ export default createReactClass({ onCancel={this.onCancel} /> ); - }, -}); + } +} diff --git a/src/components/views/dialogs/ErrorDialog.js b/src/components/views/dialogs/ErrorDialog.js index fbc55094570..acebdcd854e 100644 --- a/src/components/views/dialogs/ErrorDialog.js +++ b/src/components/views/dialogs/ErrorDialog.js @@ -26,14 +26,12 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; -export default createReactClass({ - displayName: 'ErrorDialog', - propTypes: { +export default class ErrorDialog extends React.Component { + static propTypes = { title: PropTypes.string, description: PropTypes.oneOfType([ PropTypes.element, @@ -43,18 +41,16 @@ export default createReactClass({ focus: PropTypes.bool, onFinished: PropTypes.func.isRequired, headerImage: PropTypes.string, - }, + }; - getDefaultProps: function() { - return { - focus: true, - title: null, - description: null, - button: null, - }; - }, + static defaultProps = { + focus: true, + title: null, + description: null, + button: null, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( ); - }, -}); + } +} diff --git a/src/components/views/dialogs/InfoDialog.js b/src/components/views/dialogs/InfoDialog.js index b63f6ba9c65..8125bc3edd4 100644 --- a/src/components/views/dialogs/InfoDialog.js +++ b/src/components/views/dialogs/InfoDialog.js @@ -17,15 +17,13 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import classNames from "classnames"; -export default createReactClass({ - displayName: 'InfoDialog', - propTypes: { +export default class InfoDialog extends React.Component { + static propTypes = { className: PropTypes.string, title: PropTypes.string, description: PropTypes.node, @@ -33,21 +31,19 @@ export default createReactClass({ onFinished: PropTypes.func, hasCloseButton: PropTypes.bool, onKeyDown: PropTypes.func, - }, + }; - getDefaultProps: function() { - return { - title: '', - description: '', - hasCloseButton: false, - }; - }, + static defaultProps = { + title: '', + description: '', + hasCloseButton: false, + }; - onFinished: function() { + onFinished = () => { this.props.onFinished(); - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return ( @@ -69,5 +65,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/dialogs/InteractiveAuthDialog.js b/src/components/views/dialogs/InteractiveAuthDialog.js index b06ce63ecdd..22291225adc 100644 --- a/src/components/views/dialogs/InteractiveAuthDialog.js +++ b/src/components/views/dialogs/InteractiveAuthDialog.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; @@ -27,10 +26,8 @@ import AccessibleButton from '../elements/AccessibleButton'; import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth"; import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents"; -export default createReactClass({ - displayName: 'InteractiveAuthDialog', - - propTypes: { +export default class InteractiveAuthDialog extends React.Component { + static propTypes = { // matrix client to use for UI auth requests matrixClient: PropTypes.object.isRequired, @@ -70,19 +67,17 @@ export default createReactClass({ // // Default is defined in _getDefaultDialogAesthetics() aestheticsForStagePhases: PropTypes.object, - }, + }; - getInitialState: function() { - return { - authError: null, + state = { + authError: null, - // See _onUpdateStagePhase() - uiaStage: null, - uiaStagePhase: null, - }; - }, + // See _onUpdateStagePhase() + uiaStage: null, + uiaStagePhase: null, + }; - _getDefaultDialogAesthetics: function() { + _getDefaultDialogAesthetics() { const ssoAesthetics = { [SSOAuthEntry.PHASE_PREAUTH]: { title: _t("Use Single Sign On to continue"), @@ -102,9 +97,9 @@ export default createReactClass({ [SSOAuthEntry.LOGIN_TYPE]: ssoAesthetics, [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: ssoAesthetics, }; - }, + } - _onAuthFinished: function(success, result) { + _onAuthFinished = (success, result) => { if (success) { this.props.onFinished(true, result); } 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() this.setState({uiaStage: newStage, uiaStagePhase: newPhase}); - }, + }; - _onDismissClick: function() { + _onDismissClick = () => { this.props.onFinished(false); - }, + }; - render: function() { + render() { const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth"); const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); @@ -190,5 +185,5 @@ export default createReactClass({ { content } ); - }, -}); + } +} diff --git a/src/components/views/dialogs/QuestionDialog.js b/src/components/views/dialogs/QuestionDialog.js index 07a1eae5d56..d6de60195f9 100644 --- a/src/components/views/dialogs/QuestionDialog.js +++ b/src/components/views/dialogs/QuestionDialog.js @@ -16,14 +16,12 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; -export default createReactClass({ - displayName: 'QuestionDialog', - propTypes: { +export default class QuestionDialog extends React.Component { + static propTypes = { title: PropTypes.string, description: PropTypes.node, extraButtons: PropTypes.node, @@ -34,29 +32,27 @@ export default createReactClass({ headerImage: PropTypes.string, quitOnly: PropTypes.bool, // quitOnly doesn't show the cancel button just the quit [x]. fixedWidth: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - title: "", - description: "", - extraButtons: null, - focus: true, - hasCancelButton: true, - danger: false, - quitOnly: false, - }; - }, + static defaultProps = { + title: "", + description: "", + extraButtons: null, + focus: true, + hasCancelButton: true, + danger: false, + quitOnly: false, + }; - onOk: function() { + onOk = () => { this.props.onFinished(true); - }, + }; - onCancel: function() { + onCancel = () => { this.props.onFinished(false); - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); let primaryButtonClass = ""; @@ -88,5 +84,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/dialogs/RoomUpgradeDialog.js b/src/components/views/dialogs/RoomUpgradeDialog.js index c45d82303b6..85e97444ed6 100644 --- a/src/components/views/dialogs/RoomUpgradeDialog.js +++ b/src/components/views/dialogs/RoomUpgradeDialog.js @@ -15,38 +15,33 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; -export default createReactClass({ - displayName: 'RoomUpgradeDialog', - - propTypes: { +export default class RoomUpgradeDialog extends React.Component { + static propTypes = { room: PropTypes.object.isRequired, onFinished: PropTypes.func.isRequired, - }, + }; + + state = { + busy: true, + }; - componentDidMount: async function() { + async componentDidMount() { const recommended = await this.props.room.getRecommendedVersion(); this._targetVersion = recommended.version; this.setState({busy: false}); - }, - - getInitialState: function() { - return { - busy: true, - }; - }, + } - _onCancelClick: function() { + _onCancelClick = () => { this.props.onFinished(false); - }, + }; - _onUpgradeClick: function() { + _onUpgradeClick = () => { this.setState({busy: true}); MatrixClientPeg.get().upgradeRoom(this.props.room.roomId, this._targetVersion).then(() => { this.props.onFinished(true); @@ -59,9 +54,9 @@ export default createReactClass({ }).finally(() => { this.setState({busy: false}); }); - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const Spinner = sdk.getComponent('views.elements.Spinner'); @@ -106,5 +101,5 @@ export default createReactClass({ {buttons} ); - }, -}); + } +} diff --git a/src/components/views/dialogs/SessionRestoreErrorDialog.js b/src/components/views/dialogs/SessionRestoreErrorDialog.js index 37061720855..bae6b19fbe5 100644 --- a/src/components/views/dialogs/SessionRestoreErrorDialog.js +++ b/src/components/views/dialogs/SessionRestoreErrorDialog.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; @@ -25,20 +24,18 @@ import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; -export default createReactClass({ - displayName: 'SessionRestoreErrorDialog', - - propTypes: { +export default class SessionRestoreErrorDialog extends React.Component { + static propTypes = { error: PropTypes.string.isRequired, onFinished: PropTypes.func.isRequired, - }, + }; - _sendBugReport: function() { + _sendBugReport = () => { const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog"); Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {}); - }, + }; - _onClearStorageClick: function() { + _onClearStorageClick = () => { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); Modal.createTrackedDialog('Session Restore Confirm Logout', '', QuestionDialog, { title: _t("Sign out"), @@ -48,15 +45,15 @@ export default createReactClass({ danger: true, onFinished: this.props.onFinished, }); - }, + }; - _onRefreshClick: function() { + _onRefreshClick = () => { // Is this likely to help? Probably not, but giving only one button // that clears your storage seems awful. window.location.reload(true); - }, + }; - render: function() { + render() { const brand = SdkConfig.get().brand; const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); @@ -110,5 +107,5 @@ export default createReactClass({ { dialogButtons } ); - }, -}); + } +} diff --git a/src/components/views/dialogs/SetEmailDialog.js b/src/components/views/dialogs/SetEmailDialog.js index 2e38d6a7c40..75a6a74352b 100644 --- a/src/components/views/dialogs/SetEmailDialog.js +++ b/src/components/views/dialogs/SetEmailDialog.js @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import * as Email from '../../../email'; @@ -30,26 +29,23 @@ import Modal from '../../../Modal'; * * On success, `onFinished(true)` is called. */ -export default createReactClass({ - displayName: 'SetEmailDialog', - propTypes: { +export default class SetEmailDialog extends React.Component { + static propTypes = { onFinished: PropTypes.func.isRequired, - }, + }; - getInitialState: function() { - return { - emailAddress: '', - emailBusy: false, - }; - }, + state = { + emailAddress: '', + emailBusy: false, + }; - onEmailAddressChanged: function(value) { + onEmailAddressChanged = value => { this.setState({ emailAddress: value, }); - }, + }; - onSubmit: function() { + onSubmit = () => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); @@ -81,21 +77,21 @@ export default createReactClass({ }); }); this.setState({emailBusy: true}); - }, + }; - onCancelled: function() { + onCancelled = () => { this.props.onFinished(false); - }, + }; - onEmailDialogFinished: function(ok) { + onEmailDialogFinished = ok => { if (ok) { this.verifyEmailAddress(); } else { this.setState({emailBusy: false}); } - }, + }; - verifyEmailAddress: function() { + verifyEmailAddress() { this._addThreepid.checkEmailLinkClicked().then(() => { this.props.onFinished(true); }, (err) => { @@ -119,9 +115,9 @@ export default createReactClass({ }); } }); - }, + } - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const Spinner = sdk.getComponent('elements.Spinner'); const EditableText = sdk.getComponent('elements.EditableText'); @@ -161,5 +157,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js index f99d065e7ea..c580575f1a4 100644 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -16,7 +16,6 @@ limitations under the License. */ import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; @@ -34,18 +33,22 @@ const USERNAME_CHECK_DEBOUNCE_MS = 250; * * On success, `onFinished(true, newDisplayName)` is called. */ -export default createReactClass({ - displayName: 'SetMxIdDialog', - propTypes: { +export default class SetMxIdDialog extends React.Component { + static propTypes = { onFinished: PropTypes.func.isRequired, // Called when the user requests to register with a different homeserver onDifferentServerClicked: PropTypes.func.isRequired, // Called if the user wants to switch to login instead onLoginClick: PropTypes.func.isRequired, - }, + }; - getInitialState: function() { - return { + constructor(props) { + super(props); + + this._input_value = createRef(); + this._uiAuth = createRef(); + + this.state = { // The entered username username: '', // Indicate ongoing work on the username @@ -60,21 +63,15 @@ export default createReactClass({ // Indicate error with auth authError: '', }; - }, - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { - this._input_value = createRef(); - this._uiAuth = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { this._input_value.current.select(); this._matrixClient = MatrixClientPeg.get(); - }, + } - onValueChange: function(ev) { + onValueChange = ev => { this.setState({ username: ev.target.value, usernameBusy: true, @@ -99,24 +96,24 @@ export default createReactClass({ }); }, USERNAME_CHECK_DEBOUNCE_MS); }); - }, + }; - onKeyUp: function(ev) { + onKeyUp = ev => { if (ev.key === Key.ENTER) { this.onSubmit(); } - }, + }; - onSubmit: function(ev) { + onSubmit = ev => { if (this._uiAuth.current) { this._uiAuth.current.tryContinue(); } this.setState({ doingUIAuth: true, }); - }, + }; - _doUsernameCheck: function() { + _doUsernameCheck() { // We do a quick check ahead of the username availability API to ensure the // user ID roughly looks okay from a Matrix perspective. if (!SAFE_LOCALPART_REGEX.test(this.state.username)) { @@ -167,13 +164,13 @@ export default createReactClass({ this.setState(newState); }, ); - }, + } - _generatePassword: function() { + _generatePassword() { return Math.random().toString(36).slice(2); - }, + } - _makeRegisterRequest: function(auth) { + _makeRegisterRequest = auth => { // Not upgrading - changing mxids const guestAccessToken = null; if (!this._generatedPassword) { @@ -187,9 +184,9 @@ export default createReactClass({ {}, guestAccessToken, ); - }, + }; - _onUIAuthFinished: function(success, response) { + _onUIAuthFinished = (success, response) => { this.setState({ doingUIAuth: false, }); @@ -207,9 +204,9 @@ export default createReactClass({ accessToken: response.access_token, password: this._generatedPassword, }); - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth'); @@ -303,5 +300,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/dialogs/SetPasswordDialog.js b/src/components/views/dialogs/SetPasswordDialog.js index fcc6e67656d..3649190ac90 100644 --- a/src/components/views/dialogs/SetPasswordDialog.js +++ b/src/components/views/dialogs/SetPasswordDialog.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -63,32 +62,25 @@ const WarmFuzzy = function(props) { * * On success, `onFinished()` when finished */ -export default createReactClass({ - displayName: 'SetPasswordDialog', - propTypes: { +export default class SetPasswordDialog extends React.Component { + static propTypes = { onFinished: PropTypes.func.isRequired, - }, + }; - getInitialState: function() { - return { - error: null, - }; - }, + state = { + error: null, + }; - componentDidMount: function() { - console.info('SetPasswordDialog component did mount'); - }, - - _onPasswordChanged: function(res) { + _onPasswordChanged = res => { Modal.createDialog(WarmFuzzy, { didSetEmail: res.didSetEmail, onFinished: () => { this.props.onFinished(); }, }); - }, + }; - _onPasswordChangeError: function(err) { + _onPasswordChangeError = err => { let errMsg = err.error || ""; if (err.httpStatus === 403) { errMsg = _t('Failed to change password. Is your password correct?'); @@ -101,9 +93,9 @@ export default createReactClass({ this.setState({ error: errMsg, }); - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const ChangePassword = sdk.getComponent('views.settings.ChangePassword'); @@ -132,5 +124,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/dialogs/TextInputDialog.js b/src/components/views/dialogs/TextInputDialog.js index d7ca3f144dd..571ed7e413f 100644 --- a/src/components/views/dialogs/TextInputDialog.js +++ b/src/components/views/dialogs/TextInputDialog.js @@ -15,14 +15,12 @@ limitations under the License. */ import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import Field from "../elements/Field"; -export default createReactClass({ - displayName: 'TextInputDialog', - propTypes: { +export default class TextInputDialog extends React.Component { + static propTypes = { title: PropTypes.string, description: PropTypes.oneOfType([ PropTypes.element, @@ -36,39 +34,36 @@ export default createReactClass({ hasCancel: PropTypes.bool, validator: PropTypes.func, // result of withValidation fixedWidth: PropTypes.bool, - }, - - getDefaultProps: function() { - return { - title: "", - value: "", - description: "", - focus: true, - hasCancel: true, - }; - }, + }; + + static defaultProps = { + title: "", + value: "", + description: "", + focus: true, + hasCancel: true, + }; - getInitialState: function() { - return { + constructor(props) { + super(props); + + this._field = createRef(); + + this.state = { value: this.props.value, valid: false, }; - }, - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { - this._field = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { if (this.props.focus) { // Set the cursor at the end of the text input // this._field.current.value = this.props.value; this._field.current.focus(); } - }, + } - onOk: async function(ev) { + onOk = async ev => { ev.preventDefault(); if (this.props.validator) { await this._field.current.validate({ allowEmpty: false }); @@ -80,27 +75,27 @@ export default createReactClass({ } } this.props.onFinished(true, this.state.value); - }, + }; - onCancel: function() { + onCancel = () => { this.props.onFinished(false); - }, + }; - onChange: function(ev) { + onChange = ev => { this.setState({ value: ev.target.value, }); - }, + }; - onValidate: async function(fieldState) { + onValidate = async fieldState => { const result = await this.props.validator(fieldState); this.setState({ valid: result.valid, }); return result; - }, + }; - render: function() { + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return ( @@ -137,5 +132,5 @@ export default createReactClass({ /> ); - }, -}); + } +} diff --git a/src/components/views/elements/ActionButton.js b/src/components/views/elements/ActionButton.js index 7536d66653d..bec016bce06 100644 --- a/src/components/views/elements/ActionButton.js +++ b/src/components/views/elements/ActionButton.js @@ -16,16 +16,13 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import AccessibleButton from './AccessibleButton'; import dis from '../../../dispatcher/dispatcher'; import * as sdk from '../../../index'; import Analytics from '../../../Analytics'; -export default createReactClass({ - displayName: 'RoleButton', - - propTypes: { +export default class ActionButton extends React.Component { + static propTypes = { size: PropTypes.string, tooltip: PropTypes.bool, action: PropTypes.string.isRequired, @@ -33,39 +30,35 @@ export default createReactClass({ label: PropTypes.string.isRequired, iconPath: PropTypes.string, className: PropTypes.string, - }, + }; - getDefaultProps: function() { - return { - size: "25", - tooltip: false, - }; - }, + static defaultProps = { + size: "25", + tooltip: false, + }; - getInitialState: function() { - return { - showTooltip: false, - }; - }, + state = { + showTooltip: false, + }; - _onClick: function(ev) { + _onClick = (ev) => { ev.stopPropagation(); Analytics.trackEvent('Action Button', 'click', this.props.action); dis.dispatch({action: this.props.action}); - }, + }; - _onMouseEnter: function() { + _onMouseEnter = () => { if (this.props.tooltip) this.setState({showTooltip: true}); if (this.props.mouseOverAction) { dis.dispatch({action: this.props.mouseOverAction}); } - }, + }; - _onMouseLeave: function() { + _onMouseLeave = () => { this.setState({showTooltip: false}); - }, + }; - render: function() { + render() { const TintableSvg = sdk.getComponent("elements.TintableSvg"); let tooltip; @@ -94,5 +87,5 @@ export default createReactClass({ { tooltip } ); - }, -}); + } +} diff --git a/src/components/views/elements/AddressSelector.js b/src/components/views/elements/AddressSelector.js index ab29723a45a..45cdbeced85 100644 --- a/src/components/views/elements/AddressSelector.js +++ b/src/components/views/elements/AddressSelector.js @@ -17,15 +17,12 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import classNames from 'classnames'; import { UserAddressType } from '../../../UserAddress'; -export default createReactClass({ - displayName: 'AddressSelector', - - propTypes: { +export default class AddressSelector extends React.Component { + static propTypes = { onSelected: PropTypes.func.isRequired, // List of the addresses to display @@ -37,90 +34,91 @@ export default createReactClass({ // Element to put as a header on top of the list header: PropTypes.node, - }, + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this.state = { selected: this.props.selected === undefined ? 0 : this.props.selected, hover: false, }; - }, + } // 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 const selected = this.state.selected; const maxSelected = this._maxSelected(props.addressList); if (selected > maxSelected) { this.setState({ selected: maxSelected }); } - }, + } - componentDidUpdate: function() { + componentDidUpdate() { // As the user scrolls with the arrow keys keep the selected item // at the top of the window. if (this.scrollElement && this.props.addressList.length > 0 && !this.state.hover) { const elementHeight = this.addressListElement.getBoundingClientRect().height; this.scrollElement.scrollTop = (this.state.selected * elementHeight) - elementHeight; } - }, + } - moveSelectionTop: function() { + moveSelectionTop = () => { if (this.state.selected > 0) { this.setState({ selected: 0, hover: false, }); } - }, + }; - moveSelectionUp: function() { + moveSelectionUp = () => { if (this.state.selected > 0) { this.setState({ selected: this.state.selected - 1, hover: false, }); } - }, + }; - moveSelectionDown: function() { + moveSelectionDown = () => { if (this.state.selected < this._maxSelected(this.props.addressList)) { this.setState({ selected: this.state.selected + 1, hover: false, }); } - }, + }; - chooseSelection: function() { + chooseSelection = () => { this.selectAddress(this.state.selected); - }, + }; - onClick: function(index) { + onClick = index => { this.selectAddress(index); - }, + }; - onMouseEnter: function(index) { + onMouseEnter = index => { this.setState({ selected: index, hover: true, }); - }, + }; - onMouseLeave: function() { + onMouseLeave = () => { this.setState({ hover: false }); - }, + }; - selectAddress: function(index) { + selectAddress = index => { // Only try to select an address if one exists if (this.props.addressList.length !== 0) { this.props.onSelected(index); this.setState({ hover: false }); } - }, + }; - createAddressListTiles: function() { - const self = this; + createAddressListTiles() { const AddressTile = sdk.getComponent("elements.AddressTile"); const maxSelected = this._maxSelected(this.props.addressList); const addressList = []; @@ -157,15 +155,15 @@ export default createReactClass({ } } return addressList; - }, + } - _maxSelected: function(list) { + _maxSelected(list) { const listSize = list.length === 0 ? 0 : list.length - 1; const maxSelected = listSize > (this.props.truncateAt - 1) ? (this.props.truncateAt - 1) : listSize; return maxSelected; - }, + } - render: function() { + render() { const classes = classNames({ "mx_AddressSelector": true, "mx_AddressSelector_empty": this.props.addressList.length === 0, @@ -177,5 +175,5 @@ export default createReactClass({ { this.createAddressListTiles() } ); - }, -}); + } +} diff --git a/src/components/views/elements/AddressTile.js b/src/components/views/elements/AddressTile.js index e5ea2e5d200..dc6c6b2914d 100644 --- a/src/components/views/elements/AddressTile.js +++ b/src/components/views/elements/AddressTile.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import classNames from 'classnames'; import * as sdk from "../../../index"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; @@ -25,25 +24,21 @@ import { _t } from '../../../languageHandler'; import { UserAddressType } from '../../../UserAddress.js'; -export default createReactClass({ - displayName: 'AddressTile', - - propTypes: { +export default class AddressTile extends React.Component { + static propTypes = { address: UserAddressType.isRequired, canDismiss: PropTypes.bool, onDismissed: PropTypes.func, justified: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - canDismiss: false, - onDismissed: function() {}, // NOP - justified: false, - }; - }, + static defaultProps = { + canDismiss: false, + onDismissed: function() {}, // NOP + justified: false, + }; - render: function() { + render() { const address = this.props.address; const name = address.displayName || address.address; @@ -144,5 +139,5 @@ export default createReactClass({ { dismiss } ); - }, -}); + } +} diff --git a/src/components/views/elements/DialogButtons.js b/src/components/views/elements/DialogButtons.js index 9223b5ade86..001292b6b74 100644 --- a/src/components/views/elements/DialogButtons.js +++ b/src/components/views/elements/DialogButtons.js @@ -18,16 +18,13 @@ limitations under the License. import React from "react"; import PropTypes from "prop-types"; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; /** * Basic container for buttons in modal dialogs. */ -export default createReactClass({ - displayName: "DialogButtons", - - propTypes: { +export default class DialogButtons extends React.Component { + static propTypes = { // The primary button which is styled differently and has default focus. primaryButton: PropTypes.node.isRequired, @@ -57,20 +54,18 @@ export default createReactClass({ // disables only the primary button primaryDisabled: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - hasCancel: true, - disabled: false, - }; - }, + static defaultProps = { + hasCancel: true, + disabled: false, + }; - _onCancelClick: function() { + _onCancelClick = () => { this.props.onCancel(); - }, + }; - render: function() { + render() { let primaryButtonClassName = "mx_Dialog_primary"; if (this.props.primaryButtonClass) { primaryButtonClassName += " " + this.props.primaryButtonClass; @@ -104,5 +99,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/elements/EditableText.js b/src/components/views/elements/EditableText.js index 82f5eef1250..5a2c042b831 100644 --- a/src/components/views/elements/EditableText.js +++ b/src/components/views/elements/EditableText.js @@ -17,13 +17,10 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {Key} from "../../../Keyboard"; -export default createReactClass({ - displayName: 'EditableText', - - propTypes: { +export default class EditableText extends React.Component { + static propTypes = { onValueChanged: PropTypes.func, initialValue: PropTypes.string, label: PropTypes.string, @@ -36,60 +33,62 @@ export default createReactClass({ // Will cause onValueChanged(value, true) to fire on blur blurToSubmit: PropTypes.bool, editable: PropTypes.bool, - }, + }; - Phases: { + static Phases = { Display: "display", Edit: "edit", - }, - - getDefaultProps: function() { - return { - onValueChanged: function() {}, - initialValue: '', - label: '', - placeholder: '', - editable: true, - className: "mx_EditableText", - placeholderClassName: "mx_EditableText_placeholder", - blurToSubmit: false, - }; - }, + }; + + static defaultProps = { + onValueChanged() {}, + initialValue: '', + label: '', + placeholder: '', + editable: true, + className: "mx_EditableText", + placeholderClassName: "mx_EditableText_placeholder", + blurToSubmit: false, + }; + + constructor(props) { + super(props); + + // we track value as an JS object field rather than in React state + // as React doesn't play nice with contentEditable. + this.value = ''; + this.placeholder = false; + + this._editable_div = createRef(); - getInitialState: function() { - return { - phase: this.Phases.Display, + this.state = { + phase: EditableText.Phases.Display, }; - }, + } + + + state = { + phase: EditableText.Phases.Display, + }; // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps: function(nextProps) { + UNSAFE_componentWillReceiveProps(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 - // as React doesn't play nice with contentEditable. - this.value = ''; - this.placeholder = false; - - this._editable_div = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { this.value = this.props.initialValue; if (this._editable_div.current) { this.showPlaceholder(!this.value); } - }, + } - showPlaceholder: function(show) { + showPlaceholder = show => { if (show) { this._editable_div.current.textContent = this.props.placeholder; 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.placeholder = false; } - }, + }; - getValue: function() { - return this.value; - }, + getValue = () => this.value; - setValue: function(value) { + setValue = value => { this.value = value; this.showPlaceholder(!this.value); - }, + }; - edit: function() { + edit = () => { this.setState({ - phase: this.Phases.Edit, + phase: EditableText.Phases.Edit, }); - }, + }; - cancelEdit: function() { + cancelEdit = () => { this.setState({ - phase: this.Phases.Display, + phase: EditableText.Phases.Display, }); this.value = this.props.initialValue; this.showPlaceholder(!this.value); this.onValueChanged(false); this._editable_div.current.blur(); - }, + }; - onValueChanged: function(shouldSubmit) { + onValueChanged = 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); if (this.placeholder) { @@ -145,9 +142,9 @@ export default createReactClass({ } // 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); if (!ev.target.textContent) { @@ -163,17 +160,17 @@ export default createReactClass({ } // console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder); - }, + }; - onClickDiv: function(ev) { + onClickDiv = ev => { if (!this.props.editable) return; this.setState({ - phase: this.Phases.Edit, + phase: EditableText.Phases.Edit, }); - }, + }; - onFocus: function(ev) { + onFocus = ev => { //ev.target.setSelectionRange(0, ev.target.textContent.length); const node = ev.target.childNodes[0]; @@ -186,21 +183,21 @@ export default createReactClass({ sel.removeAllRanges(); sel.addRange(range); } - }, + }; - onFinish: function(ev, shouldSubmit) { + onFinish = (ev, shouldSubmit) => { const self = this; const submit = (ev.key === Key.ENTER) || shouldSubmit; this.setState({ - phase: this.Phases.Display, + phase: EditableText.Phases.Display, }, () => { if (this.value !== this.props.initialValue) { self.onValueChanged(submit); } }); - }, + }; - onBlur: function(ev) { + onBlur = ev => { const sel = window.getSelection(); sel.removeAllRanges(); @@ -211,13 +208,13 @@ export default createReactClass({ } this.showPlaceholder(!this.value); - }, + }; - render: function() { + render() { const {className, editable, initialValue, label, labelClassName} = this.props; 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 editableEl =
{ label || initialValue } @@ -234,5 +231,5 @@ export default createReactClass({ } return editableEl; - }, -}); + } +} diff --git a/src/components/views/elements/InlineSpinner.js b/src/components/views/elements/InlineSpinner.js index ce3c738f3b9..73316157f45 100644 --- a/src/components/views/elements/InlineSpinner.js +++ b/src/components/views/elements/InlineSpinner.js @@ -15,14 +15,11 @@ limitations under the License. */ import React from "react"; -import createReactClass from 'create-react-class'; import {_t} from "../../../languageHandler"; import SettingsStore from "../../../settings/SettingsStore"; -export default createReactClass({ - displayName: 'InlineSpinner', - - render: function() { +export default class InlineSpinner extends React.Component { + render() { const w = this.props.w || 16; const h = this.props.h || 16; const imgClass = this.props.imgClassName || ""; @@ -45,5 +42,5 @@ export default createReactClass({ />
); - }, -}); + } +} diff --git a/src/components/views/elements/MemberEventListSummary.js b/src/components/views/elements/MemberEventListSummary.js index 956b69ca7b8..e16b52c8a27 100644 --- a/src/components/views/elements/MemberEventListSummary.js +++ b/src/components/views/elements/MemberEventListSummary.js @@ -18,17 +18,14 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; import { formatCommaSeparatedList } from '../../../utils/FormattingUtils'; import * as sdk from "../../../index"; import {MatrixEvent} from "matrix-js-sdk"; import {isValid3pidInvite} from "../../../RoomInvite"; -export default createReactClass({ - displayName: 'MemberEventListSummary', - - propTypes: { +export default class MemberEventListSummary extends React.Component { + static propTypes = { // An array of member events to summarise events: PropTypes.arrayOf(PropTypes.instanceOf(MatrixEvent)).isRequired, // An array of EventTiles to render when expanded @@ -43,17 +40,15 @@ export default createReactClass({ onToggle: PropTypes.func, // Whether or not to begin with state.expanded=true startExpanded: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - summaryLength: 1, - threshold: 3, - avatarsMaxLength: 5, - }; - }, + static defaultProps = { + summaryLength: 1, + threshold: 3, + avatarsMaxLength: 5, + }; - shouldComponentUpdate: function(nextProps) { + shouldComponentUpdate(nextProps) { // Update if // - The number of summarised events has changed // - 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.threshold ); - }, + } /** * Generate the text for users aggregated by their transition sequences (`eventAggregates`) where @@ -73,7 +68,7 @@ export default createReactClass({ * `Object.keys(eventAggregates)`. * @returns {string} the textual summary of the aggregated events that occurred. */ - _generateSummary: function(eventAggregates, orderedTransitionSequences) { + _generateSummary(eventAggregates, orderedTransitionSequences) { const summaries = orderedTransitionSequences.map((transitions) => { const userNames = eventAggregates[transitions]; const nameList = this._renderNameList(userNames); @@ -105,7 +100,7 @@ export default createReactClass({ } return summaries.join(", "); - }, + } /** * @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 * included before "and [n] others". */ - _renderNameList: function(users) { + _renderNameList(users) { return formatCommaSeparatedList(users, this.props.summaryLength); - }, + } /** * 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. * @returns {string[]} an array of transitions. */ - _getCanonicalTransitions: function(transitions) { + _getCanonicalTransitions(transitions) { const modMap = { 'joined': { 'after': 'left', @@ -155,7 +150,7 @@ export default createReactClass({ res.push(transition); } return res; - }, + } /** * 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. * @returns {object[]} an array of coalesced transitions. */ - _coalesceRepeatedTransitions: function(transitions) { + _coalesceRepeatedTransitions(transitions) { const res = []; for (let i = 0; i < transitions.length; i++) { if (res.length > 0 && res[res.length - 1].transitionType === transitions[i]) { @@ -184,7 +179,7 @@ export default createReactClass({ } } return res; - }, + } /** * For a certain transition, t, describe what happened to the users that @@ -268,11 +263,11 @@ export default createReactClass({ } return res; - }, + } - _getTransitionSequence: function(events) { + _getTransitionSequence(events) { return events.map(this._getTransition); - }, + } /** * 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` * if a transition is not recognised. */ - _getTransition: function(e) { + _getTransition(e) { if (e.mxEvent.getType() === 'm.room.third_party_invite') { // Handle 3pid invites the same as invites so they get bundled together if (!isValid3pidInvite(e.mxEvent)) { @@ -323,9 +318,9 @@ export default createReactClass({ } default: return null; } - }, + } - _getAggregate: function(userEvents) { + _getAggregate(userEvents) { // 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". // The array of display names is the array of users who went through that @@ -364,9 +359,9 @@ export default createReactClass({ names: aggregate, indices: aggregateIndices, }; - }, + } - render: function() { + render() { const eventsToRender = this.props.events; // Map user IDs to an array of objects: @@ -420,5 +415,5 @@ export default createReactClass({ children={this.props.children} summaryMembers={avatarMembers} summaryText={this._generateSummary(aggregate.names, orderedTransitionSequences)} />; - }, -}); + } +} diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js index a146debc459..bdf5f602349 100644 --- a/src/components/views/elements/PersistentApp.js +++ b/src/components/views/elements/PersistentApp.js @@ -16,49 +16,44 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import RoomViewStore from '../../../stores/RoomViewStore'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; import WidgetUtils from '../../../utils/WidgetUtils'; import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; -export default createReactClass({ - displayName: 'PersistentApp', +export default class PersistentApp extends React.Component { + state = { + roomId: RoomViewStore.getRoomId(), + persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(), + }; - getInitialState: function() { - return { - roomId: RoomViewStore.getRoomId(), - persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(), - }; - }, - - componentDidMount: function() { + componentDidMount() { this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { if (this._roomStoreToken) { this._roomStoreToken.remove(); } ActiveWidgetStore.removeListener('update', this._onActiveWidgetStoreUpdate); - }, + } - _onRoomViewStoreUpdate: function(payload) { + _onRoomViewStoreUpdate = payload => { if (RoomViewStore.getRoomId() === this.state.roomId) return; this.setState({ roomId: RoomViewStore.getRoomId(), }); - }, + }; - _onActiveWidgetStoreUpdate: function() { + _onActiveWidgetStoreUpdate = () => { this.setState({ persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(), }); - }, + }; - render: function() { + render() { if (this.state.persistentWidgetId) { const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId); if (this.state.roomId !== persistentWidgetInRoomId) { @@ -91,6 +86,6 @@ export default createReactClass({ } } return null; - }, -}); + } +} diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js index 03a1aeed85e..58537d48650 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.js @@ -16,7 +16,6 @@ See the License for the specific language governing permissions and limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import classNames from 'classnames'; @@ -32,27 +31,29 @@ import {Action} from "../../../dispatcher/actions"; // HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`) const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/; -const Pill = createReactClass({ - statics: { - isPillUrl: (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 - }, +class Pill extends React.Component { + static isPillUrl(url) { + return !!getPrimaryPermalinkEntity(url); + } - 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. type: PropTypes.string, // The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl) @@ -65,23 +66,21 @@ const Pill = createReactClass({ shouldShowPillAvatar: PropTypes.bool, // Whether to render this pill as if it were highlit by a selection isSelected: PropTypes.bool, - }, + }; - getInitialState() { - return { - // ID/alias of the room/user - resourceId: null, - // Type of pill - pillType: null, + state = { + // ID/alias of the room/user + resourceId: null, + // Type of pill + pillType: null, - // The member related to the user pill - member: null, - // The group related to the group pill - group: null, - // The room related to the room pill - room: null, - }; - }, + // The member related to the user pill + member: null, + // The group related to the group pill + group: null, + // The room related to the room pill + room: null, + }; // TODO: [REACT-WARNING] Replace with appropriate lifecycle event async UNSAFE_componentWillReceiveProps(nextProps) { @@ -155,7 +154,7 @@ const Pill = createReactClass({ } } this.setState({resourceId, pillType, member, group, room}); - }, + } componentDidMount() { this._unmounted = false; @@ -163,13 +162,13 @@ const Pill = createReactClass({ // eslint-disable-next-line new-cap this.UNSAFE_componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves. - }, + } componentWillUnmount() { this._unmounted = true; - }, + } - doProfileLookup: function(userId, member) { + doProfileLookup(userId, member) { MatrixClientPeg.get().getProfileInfo(userId).then((resp) => { if (this._unmounted) { return; @@ -188,15 +187,16 @@ const Pill = createReactClass({ }).catch((err) => { console.error('Could not retrieve profile data for ' + userId + ':', err); }); - }, + } - onUserPillClicked: function() { + onUserPillClicked = () => { dis.dispatch({ action: Action.ViewUser, member: this.state.member, }); - }, - render: function() { + }; + + render() { const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar'); const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); @@ -285,7 +285,7 @@ const Pill = createReactClass({ // Deliberately render nothing if the URL isn't recognised return null; } - }, -}); + } +} export default Pill; diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js index 948b4835d59..4d00832d8d8 100644 --- a/src/components/views/elements/PowerSelector.js +++ b/src/components/views/elements/PowerSelector.js @@ -16,16 +16,13 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as Roles from '../../../Roles'; import { _t } from '../../../languageHandler'; import Field from "./Field"; import {Key} from "../../../Keyboard"; -export default createReactClass({ - displayName: 'PowerSelector', - - propTypes: { +export default class PowerSelector extends React.Component { + static propTypes = { value: PropTypes.number.isRequired, // The maximum value that can be set with the power selector maxValue: PropTypes.number.isRequired, @@ -42,10 +39,17 @@ export default createReactClass({ // The name to annotate the selector with label: PropTypes.string, - }, + } + + static defaultProps = { + maxValue: Infinity, + usersDefault: 0, + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this.state = { levelRoleMap: {}, // List of power levels to show in the drop-down options: [], @@ -53,26 +57,16 @@ export default createReactClass({ customValue: this.props.value, selectValue: 0, }; - }, - - getDefaultProps: function() { - return { - maxValue: Infinity, - usersDefault: 0, - }; - }, - componentDidMount: function() { - // TODO: [REACT-WARNING] Move this to class constructor this._initStateFromProps(this.props); - }, + } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps: function(newProps) { + UNSAFE_componentWillReceiveProps(newProps) { this._initStateFromProps(newProps); - }, + } - _initStateFromProps: function(newProps) { + _initStateFromProps(newProps) { // This needs to be done now because levelRoleMap has translated strings const levelRoleMap = Roles.levelRoleMap(newProps.usersDefault); const options = Object.keys(levelRoleMap).filter(level => { @@ -92,9 +86,9 @@ export default createReactClass({ customLevel: newProps.value, selectValue: isCustom ? "SELECT_VALUE_CUSTOM" : newProps.value, }); - }, + } - onSelectChange: function(event) { + onSelectChange = event => { const isCustom = event.target.value === "SELECT_VALUE_CUSTOM"; if (isCustom) { this.setState({custom: true}); @@ -102,20 +96,20 @@ export default createReactClass({ this.props.onChange(event.target.value, this.props.powerLevelKey); this.setState({selectValue: event.target.value}); } - }, + }; - onCustomChange: function(event) { + onCustomChange = event => { this.setState({customValue: event.target.value}); - }, + }; - onCustomBlur: function(event) { + onCustomBlur = event => { event.preventDefault(); event.stopPropagation(); this.props.onChange(parseInt(this.state.customValue), this.props.powerLevelKey); - }, + }; - onCustomKeyDown: function(event) { + onCustomKeyDown = event => { if (event.key === Key.ENTER) { event.preventDefault(); event.stopPropagation(); @@ -127,9 +121,9 @@ export default createReactClass({ // handle the onBlur safely. event.target.blur(); } - }, + }; - render: function() { + render() { let picker; const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label; if (this.state.custom) { @@ -166,5 +160,5 @@ export default createReactClass({ { picker } ); - }, -}); + } +} diff --git a/src/components/views/elements/TagTile.js b/src/components/views/elements/TagTile.js index db5eedc274a..b1a526eb0d8 100644 --- a/src/components/views/elements/TagTile.js +++ b/src/components/views/elements/TagTile.js @@ -18,7 +18,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import classNames from 'classnames'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; @@ -37,10 +36,8 @@ import SettingsStore from "../../../settings/SettingsStore"; // - Rooms that are part of the group // - Direct messages with members of the group // with the intention that this could be expanded to arbitrary tags in future. -export default createReactClass({ - displayName: 'TagTile', - - propTypes: { +export default class TagTile extends React.Component { + static propTypes = { // A string tag such as "m.favourite" or a group ID such as "+groupid:domain.bla" // For now, only group IDs are handled. tag: PropTypes.string, @@ -48,20 +45,16 @@ export default createReactClass({ openMenu: PropTypes.func, menuDisplayed: PropTypes.bool, selected: PropTypes.bool, - }, + }; - statics: { - contextType: MatrixClientContext, - }, + static contextType = MatrixClientContext; - getInitialState() { - return { - // Whether the mouse is over the tile - hover: false, - // The profile data of the group if this.props.tag is a group ID - profile: null, - }; - }, + state = { + // Whether the mouse is over the tile + hover: false, + // The profile data of the group if this.props.tag is a group ID + profile: null, + }; componentDidMount() { this.unmounted = false; @@ -71,16 +64,16 @@ export default createReactClass({ // New rooms or members may have been added to the group, fetch async this._refreshGroup(this.props.tag); } - }, + } componentWillUnmount() { this.unmounted = true; if (this.props.tag[0] === '+') { FlairStore.removeListener('updateGroupProfile', this._onFlairStoreUpdated); } - }, + } - _onFlairStoreUpdated() { + _onFlairStoreUpdated = () => { if (this.unmounted) return; FlairStore.getGroupProfileCached( this.context, @@ -91,14 +84,14 @@ export default createReactClass({ }).catch((err) => { console.warn('Could not fetch group profile for ' + this.props.tag, err); }); - }, + }; _refreshGroup(groupId) { GroupStore.refreshGroupRooms(groupId); GroupStore.refreshGroupMembers(groupId); - }, + } - onClick: function(e) { + onClick = e => { e.preventDefault(); e.stopPropagation(); dis.dispatch({ @@ -111,27 +104,27 @@ export default createReactClass({ // New rooms or members may have been added to the group, fetch async this._refreshGroup(this.props.tag); } - }, + }; - onMouseOver: function() { + onMouseOver = () => { if (SettingsStore.getValue("feature_communities_v2_prototypes")) return; this.setState({ hover: true }); - }, + }; - onMouseLeave: function() { + onMouseLeave = () => { this.setState({ hover: false }); - }, + }; - openMenu: function(e) { + openMenu = e => { // Prevent the TagTile onClick event firing as well e.stopPropagation(); e.preventDefault(); if (SettingsStore.getValue("feature_communities_v2_prototypes")) return; this.setState({ hover: false }); this.props.openMenu(); - }, + }; - render: function() { + render() { const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const profile = this.state.profile || {}; const name = profile.name || this.props.tag; @@ -192,5 +185,5 @@ export default createReactClass({ {badgeElement} ; - }, -}); + } +} diff --git a/src/components/views/elements/TintableSvg.js b/src/components/views/elements/TintableSvg.js index 66625c7b87f..df55b0a8542 100644 --- a/src/components/views/elements/TintableSvg.js +++ b/src/components/views/elements/TintableSvg.js @@ -17,49 +17,44 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import Tinter from "../../../Tinter"; -const TintableSvg = createReactClass({ - displayName: 'TintableSvg', - - propTypes: { +class TintableSvg extends React.Component { + static propTypes = { src: PropTypes.string.isRequired, width: PropTypes.string.isRequired, height: PropTypes.string.isRequired, className: PropTypes.string, - }, + }; - statics: { - // list of currently mounted TintableSvgs - mounts: {}, - idSequence: 0, - }, + // list of currently mounted TintableSvgs + static mounts = {}; + static idSequence = 0; - componentDidMount: function() { + componentDidMount() { this.fixups = []; this.id = TintableSvg.idSequence++; TintableSvg.mounts[this.id] = this; - }, + } - componentWillUnmount: function() { + componentWillUnmount() { delete TintableSvg.mounts[this.id]; - }, + } - tint: function() { + tint = () => { // TODO: only bother running this if the global tint settings have changed // since we loaded! Tinter.applySvgFixups(this.fixups); - }, + }; - onLoad: function(event) { + onLoad = event => { // console.log("TintableSvg.onLoad for " + this.props.src); this.fixups = Tinter.calcSvgFixups([event.target]); Tinter.applySvgFixups(this.fixups); - }, + }; - render: function() { + render() { return ( ); - }, -}); + } +} // Register with the Tinter so that we will be told if the tint changes Tinter.registerTintable(function() { diff --git a/src/components/views/elements/TooltipButton.js b/src/components/views/elements/TooltipButton.js index 5c8d53fbcc2..240d763bdcc 100644 --- a/src/components/views/elements/TooltipButton.js +++ b/src/components/views/elements/TooltipButton.js @@ -16,31 +16,26 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; -export default createReactClass({ - displayName: 'TooltipButton', +export default class TooltipButton extends React.Component { + state = { + hover: false, + }; - getInitialState: function() { - return { - hover: false, - }; - }, - - onMouseOver: function() { + onMouseOver = () => { this.setState({ hover: true, }); - }, + }; - onMouseLeave: function() { + onMouseLeave = () => { this.setState({ hover: false, }); - }, + }; - render: function() { + render() { const Tooltip = sdk.getComponent("elements.Tooltip"); const tip = this.state.hover ? ); - }, -}); + } +} diff --git a/src/components/views/elements/TruncatedList.js b/src/components/views/elements/TruncatedList.js index 9ce23956388..81eb057e360 100644 --- a/src/components/views/elements/TruncatedList.js +++ b/src/components/views/elements/TruncatedList.js @@ -17,13 +17,10 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; -export default createReactClass({ - displayName: 'TruncatedList', - - propTypes: { +export default class TruncatedList extends React.Component { + static propTypes = { // The number of elements to show before truncating. If negative, no truncation is done. truncateAt: PropTypes.number, // 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. // This will be inserted after the children. createOverflowElement: PropTypes.func, - }, + }; - getDefaultProps: function() { - return { - truncateAt: 2, - createOverflowElement: function(overflowCount, totalCount) { - return ( -
{ _t("And %(count)s more...", {count: overflowCount}) }
- ); - }, - }; - }, + static defaultProps ={ + truncateAt: 2, + createOverflowElement(overflowCount, totalCount) { + return ( +
{ _t("And %(count)s more...", {count: overflowCount}) }
+ ); + }, + }; - _getChildren: function(start, end) { + _getChildren(start, end) { if (this.props.getChildren && this.props.getChildCount) { return this.props.getChildren(start, end); } else { @@ -64,9 +59,9 @@ export default createReactClass({ return c != null; }).slice(start, end); } - }, + } - _getChildCount: function() { + _getChildCount() { if (this.props.getChildren && this.props.getChildCount) { return this.props.getChildCount(); } else { @@ -74,9 +69,9 @@ export default createReactClass({ return c != null; }).length; } - }, + } - render: function() { + render() { let overflowNode = null; const totalChildren = this._getChildCount(); @@ -98,5 +93,5 @@ export default createReactClass({ { overflowNode } ); - }, -}); + } +} diff --git a/src/components/views/groups/GroupInviteTile.js b/src/components/views/groups/GroupInviteTile.js index bc5334f2de6..7efb8cf4278 100644 --- a/src/components/views/groups/GroupInviteTile.js +++ b/src/components/views/groups/GroupInviteTile.js @@ -18,7 +18,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import {_t} from '../../../languageHandler'; @@ -29,50 +28,48 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; // XXX this class copies a lot from RoomTile.js -export default createReactClass({ - displayName: 'GroupInviteTile', - - propTypes: { +export default class GroupInviteTile extends React.Component { + static propTypes: { group: PropTypes.object.isRequired, - }, + }; + + static contextType = MatrixClientContext; - statics: { - contextType: MatrixClientContext, - }, + constructor(props) { + super(props); - getInitialState: function() { - return ({ + this.state = { hover: false, badgeHover: false, menuDisplayed: false, selected: this.props.group.groupId === null, // XXX: this needs linking to LoggedInView/GroupView state - }); - }, + }; + } - onClick: function(e) { + onClick = e => { dis.dispatch({ action: 'view_group', group_id: this.props.group.groupId, }); - }, + }; - onMouseEnter: function() { + onMouseEnter = () => { const state = {hover: true}; // Only allow non-guests to access the context menu if (!this.context.isGuest()) { state.badgeHover = true; } this.setState(state); - }, + }; - onMouseLeave: function() { + onMouseLeave = () => { this.setState({ badgeHover: false, hover: false, }); - }, + }; - _showContextMenu: function(boundingClientRect) { + _showContextMenu(boundingClientRect) { // Only allow non-guests to access the context menu if (MatrixClientPeg.get().isGuest()) return; @@ -86,17 +83,17 @@ export default createReactClass({ } this.setState(state); - }, + } - onContextMenuButtonClick: function(e) { + onContextMenuButtonClick = e => { // Prevent the RoomTile onClick event firing as well e.stopPropagation(); e.preventDefault(); this._showContextMenu(e.target.getBoundingClientRect()); - }, + }; - onContextMenu: function(e) { + onContextMenu = e => { // Prevent the native context menu e.preventDefault(); @@ -105,15 +102,15 @@ export default createReactClass({ top: e.clientY, height: 0, }); - }, + }; - closeMenu: function() { + closeMenu = () => { this.setState({ contextMenuPosition: null, }); - }, + }; - render: function() { + render() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); @@ -197,5 +194,5 @@ export default createReactClass({ { contextMenu } ; - }, -}); + } +} diff --git a/src/components/views/groups/GroupMemberList.js b/src/components/views/groups/GroupMemberList.js index 031b8754096..600a466601a 100644 --- a/src/components/views/groups/GroupMemberList.js +++ b/src/components/views/groups/GroupMemberList.js @@ -16,7 +16,6 @@ See the License for the specific language governing permissions and limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; @@ -30,33 +29,29 @@ import {Action} from "../../../dispatcher/actions"; const INITIAL_LOAD_NUM_MEMBERS = 30; -export default createReactClass({ - displayName: 'GroupMemberList', - - propTypes: { +export default class GroupMemberList extends React.Component { + static propTypes = { groupId: PropTypes.string.isRequired, - }, - - getInitialState: function() { - return { - members: null, - membersError: null, - invitedMembers: null, - invitedMembersError: null, - truncateAt: INITIAL_LOAD_NUM_MEMBERS, - }; - }, - - componentDidMount: function() { + }; + + state = { + members: null, + membersError: null, + invitedMembers: null, + invitedMembersError: null, + truncateAt: INITIAL_LOAD_NUM_MEMBERS, + }; + + componentDidMount() { this._unmounted = false; this._initGroupStore(this.props.groupId); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this._unmounted = true; - }, + } - _initGroupStore: function(groupId) { + _initGroupStore(groupId) { GroupStore.registerListener(groupId, () => { this._fetchMembers(); }); @@ -73,17 +68,17 @@ export default createReactClass({ }); } }); - }, + } - _fetchMembers: function() { + _fetchMembers() { if (this._unmounted) return; this.setState({ members: GroupStore.getGroupMembers(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. const EntityTile = sdk.getComponent("rooms.EntityTile"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); @@ -94,19 +89,19 @@ export default createReactClass({ } name={text} presenceState="online" suppressOnHover={true} onClick={this._showFullMemberList} /> ); - }, + }; - _showFullMemberList: function() { + _showFullMemberList = () => { this.setState({ truncateAt: -1, }); - }, + }; - onSearchQueryChanged: function(ev) { + onSearchQueryChanged = ev => { this.setState({ searchQuery: ev.target.value }); - }, + }; - makeGroupMemberTiles: function(query, memberList, memberListError) { + makeGroupMemberTiles(query, memberList, memberListError) { if (memberListError) { return
{ _t("Failed to load group members") }
; } @@ -160,9 +155,9 @@ export default createReactClass({ > { memberTiles } ; - }, + } - onInviteToGroupButtonClick() { + onInviteToGroupButtonClick = () => { showGroupInviteDialog(this.props.groupId).then(() => { dis.dispatch({ action: Action.SetRightPanelPhase, @@ -170,9 +165,9 @@ export default createReactClass({ refireParams: { groupId: this.props.groupId }, }); }); - }, + }; - render: function() { + render() { if (this.state.fetching || this.state.fetchingInvitedMembers) { const Spinner = sdk.getComponent("elements.Spinner"); return (
@@ -230,5 +225,5 @@ export default createReactClass({ { inputBox }
); - }, -}); + } +} diff --git a/src/components/views/groups/GroupMemberTile.js b/src/components/views/groups/GroupMemberTile.js index 05e3f6ac2a8..13617cf681a 100644 --- a/src/components/views/groups/GroupMemberTile.js +++ b/src/components/views/groups/GroupMemberTile.js @@ -18,37 +18,28 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import { GroupMemberType } from '../../../groups'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -export default createReactClass({ - displayName: 'GroupMemberTile', - - propTypes: { +export default class GroupMemberTile extends React.Component { + static propTypes = { groupId: PropTypes.string.isRequired, member: GroupMemberType.isRequired, - }, - - getInitialState: function() { - return {}; - }, + }; - statics: { - contextType: MatrixClientContext, - }, + static contextType = MatrixClientContext; - onClick: function(e) { + onClick = e => { dis.dispatch({ action: 'view_group_user', member: this.props.member, groupId: this.props.groupId, }); - }, + }; - render: function() { + render() { const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const EntityTile = sdk.getComponent('rooms.EntityTile'); @@ -74,5 +65,5 @@ export default createReactClass({ powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null} /> ); - }, -}); + } +} diff --git a/src/components/views/groups/GroupPublicityToggle.js b/src/components/views/groups/GroupPublicityToggle.js index 81f0f469ef7..d42059551ea 100644 --- a/src/components/views/groups/GroupPublicityToggle.js +++ b/src/components/views/groups/GroupPublicityToggle.js @@ -16,44 +16,39 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import GroupStore from '../../../stores/GroupStore'; import ToggleSwitch from "../elements/ToggleSwitch"; -export default createReactClass({ - displayName: 'GroupPublicityToggle', - - propTypes: { +export default class GroupPublicityToggle extends React.Component { + static propTypes = { groupId: PropTypes.string.isRequired, - }, + }; - getInitialState() { - return { - busy: false, - ready: false, - isGroupPublicised: false, // assume false as expects a boolean - }; - }, + state = { + busy: false, + ready: false, + isGroupPublicised: false, // assume false as expects a boolean + }; - componentDidMount: function() { + componentDidMount() { this._initGroupStore(this.props.groupId); - }, + } - _initGroupStore: function(groupId) { + _initGroupStore(groupId) { this._groupStoreToken = GroupStore.registerListener(groupId, () => { this.setState({ isGroupPublicised: Boolean(GroupStore.getGroupPublicity(groupId)), ready: GroupStore.isStateReady(groupId, GroupStore.STATE_KEY.Summary), }); }); - }, + } componentWillUnmount() { if (this._groupStoreToken) this._groupStoreToken.unregister(); - }, + } - _onPublicityToggle: function() { + _onPublicityToggle = () => { this.setState({ busy: true, // Optimistic early update @@ -64,7 +59,7 @@ export default createReactClass({ busy: false, }); }); - }, + }; render() { const GroupTile = sdk.getComponent('groups.GroupTile'); @@ -76,5 +71,5 @@ export default createReactClass({ disabled={!this.state.ready || this.state.busy} onChange={this._onPublicityToggle} /> ; - }, -}); + } +} diff --git a/src/components/views/groups/GroupRoomInfo.js b/src/components/views/groups/GroupRoomInfo.js index 8c9b39675e2..e891d553aa0 100644 --- a/src/components/views/groups/GroupRoomInfo.js +++ b/src/components/views/groups/GroupRoomInfo.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import dis from '../../../dispatcher/dispatcher'; import Modal from '../../../Modal'; import * as sdk from '../../../index'; @@ -26,30 +25,24 @@ import GroupStore from '../../../stores/GroupStore'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; -export default createReactClass({ - displayName: 'GroupRoomInfo', +export default class GroupRoomInfo extends React.Component { + static contextType = MatrixClientContext; - statics: { - contextType: MatrixClientContext, - }, - - propTypes: { + static propTypes = { groupId: PropTypes.string, groupRoomId: PropTypes.string, - }, - - getInitialState: function() { - return { - isUserPrivilegedInGroup: null, - groupRoom: null, - groupRoomPublicityLoading: false, - groupRoomRemoveLoading: false, - }; - }, - - componentDidMount: function() { + }; + + state = { + isUserPrivilegedInGroup: null, + groupRoom: null, + groupRoomPublicityLoading: false, + groupRoomRemoveLoading: false, + }; + + componentDidMount() { this._initGroupStore(this.props.groupId); - }, + } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event UNSAFE_componentWillReceiveProps(newProps) { @@ -57,19 +50,19 @@ export default createReactClass({ this._unregisterGroupStore(this.props.groupId); this._initGroupStore(newProps.groupId); } - }, + } componentWillUnmount() { this._unregisterGroupStore(this.props.groupId); - }, + } _initGroupStore(groupId) { GroupStore.registerListener(groupId, this.onGroupStoreUpdated); - }, + } _unregisterGroupStore(groupId) { GroupStore.unregisterListener(this.onGroupStoreUpdated); - }, + } _updateGroupRoom() { this.setState({ @@ -77,16 +70,16 @@ export default createReactClass({ (r) => r.roomId === this.props.groupRoomId, ), }); - }, + } - onGroupStoreUpdated: function() { + onGroupStoreUpdated = () => { this.setState({ isUserPrivilegedInGroup: GroupStore.isUserPrivileged(this.props.groupId), }); this._updateGroupRoom(); - }, + }; - _onRemove: function(e) { + _onRemove = e => { const groupId = this.props.groupId; const roomName = this.state.groupRoom.displayname; e.preventDefault(); @@ -119,15 +112,15 @@ export default createReactClass({ }); }, }); - }, + }; - _onCancel: function(e) { + _onCancel = e => { dis.dispatch({ action: "view_group_room_list", }); - }, + }; - _changeGroupRoomPublicity(e) { + _changeGroupRoomPublicity = e => { const isPublic = e.target.value === "public"; this.setState({ groupRoomPublicityLoading: true, @@ -150,9 +143,9 @@ export default createReactClass({ groupRoomPublicityLoading: false, }); }); - }, + }; - render: function() { + render() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) { @@ -235,5 +228,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/groups/GroupRoomList.js b/src/components/views/groups/GroupRoomList.js index 18ab0f288a6..9bb46db47c3 100644 --- a/src/components/views/groups/GroupRoomList.js +++ b/src/components/views/groups/GroupRoomList.js @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import GroupStore from '../../../stores/GroupStore'; @@ -25,34 +24,32 @@ import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; const INITIAL_LOAD_NUM_ROOMS = 30; -export default createReactClass({ - propTypes: { +export default class GroupRoomList extends React.Component { + static propTypes = { groupId: PropTypes.string.isRequired, - }, + }; - getInitialState: function() { - return { - rooms: null, - truncateAt: INITIAL_LOAD_NUM_ROOMS, - searchQuery: "", - }; - }, + state = { + rooms: null, + truncateAt: INITIAL_LOAD_NUM_ROOMS, + searchQuery: "", + }; - componentDidMount: function() { + componentDidMount() { this._unmounted = false; this._initGroupStore(this.props.groupId); - }, + } componentWillUnmount() { this._unmounted = true; this._unregisterGroupStore(); - }, + } _unregisterGroupStore() { GroupStore.unregisterListener(this.onGroupStoreUpdated); - }, + } - _initGroupStore: function(groupId) { + _initGroupStore(groupId) { GroupStore.registerListener(groupId, this.onGroupStoreUpdated); // 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 @@ -62,16 +59,16 @@ export default createReactClass({ rooms: null, }); }); - }, + } - onGroupStoreUpdated: function() { + onGroupStoreUpdated = () => { if (this._unmounted) return; this.setState({ 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. const EntityTile = sdk.getComponent("rooms.EntityTile"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); @@ -82,25 +79,25 @@ export default createReactClass({ } name={text} presenceState="online" suppressOnHover={true} onClick={this._showFullRoomList} /> ); - }, + }; - _showFullRoomList: function() { + _showFullRoomList = () => { this.setState({ truncateAt: -1, }); - }, + }; - onSearchQueryChanged: function(ev) { + onSearchQueryChanged = ev => { this.setState({ searchQuery: ev.target.value }); - }, + }; - onAddRoomToGroupButtonClick() { + onAddRoomToGroupButtonClick = () => { showGroupAddRoomDialog(this.props.groupId).then(() => { this.forceUpdate(); }); - }, + }; - makeGroupRoomTiles: function(query) { + makeGroupRoomTiles(query) { const GroupRoomTile = sdk.getComponent("groups.GroupRoomTile"); query = (query || "").toLowerCase(); @@ -123,9 +120,9 @@ export default createReactClass({ }); return roomList; - }, + } - render: function() { + render() { if (this.state.rooms === null) { return null; } @@ -160,5 +157,5 @@ export default createReactClass({ { inputBox } ); - }, -}); + } +} diff --git a/src/components/views/groups/GroupRoomTile.js b/src/components/views/groups/GroupRoomTile.js index fd6969a49a7..85aa56d0555 100644 --- a/src/components/views/groups/GroupRoomTile.js +++ b/src/components/views/groups/GroupRoomTile.js @@ -16,29 +16,28 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import { GroupRoomType } from '../../../groups'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -const GroupRoomTile = createReactClass({ - displayName: 'GroupRoomTile', - - propTypes: { +class GroupRoomTile extends React.Component { + static propTypes = { groupId: PropTypes.string.isRequired, groupRoom: GroupRoomType.isRequired, - }, + }; + + static contextType = MatrixClientContext - onClick: function(e) { + onClick = e => { dis.dispatch({ action: 'view_group_room', groupId: this.props.groupId, groupRoomId: this.props.groupRoom.roomId, }); - }, + }; - render: function() { + render() { const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const avatarUrl = this.context.mxcUrlToHttp( @@ -63,10 +62,7 @@ const GroupRoomTile = createReactClass({ ); - }, -}); - -GroupRoomTile.contextType = MatrixClientContext; - + } +} export default GroupRoomTile; diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js index 906bcbaf997..dcc749b01f9 100644 --- a/src/components/views/groups/GroupTile.js +++ b/src/components/views/groups/GroupTile.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { Draggable, Droppable } from 'react-beautiful-dnd'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; @@ -25,53 +24,45 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; function nop() {} -const GroupTile = createReactClass({ - displayName: 'GroupTile', - - propTypes: { +class GroupTile extends React.Component { + static propTypes = { groupId: PropTypes.string.isRequired, // Whether to show the short description of the group on the tile showDescription: PropTypes.bool, // Height of the group avatar in pixels avatarHeight: PropTypes.number, draggable: PropTypes.bool, - }, + }; - statics: { - contextType: MatrixClientContext, - }, + static contextType = MatrixClientContext; - getInitialState() { - return { - profile: null, - }; - }, + static defaultProps = { + showDescription: true, + avatarHeight: 50, + draggable: true, + }; - getDefaultProps() { - return { - showDescription: true, - avatarHeight: 50, - draggable: true, - }; - }, + state = { + profile: null, + }; - componentDidMount: function() { + componentDidMount() { FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => { this.setState({profile}); }).catch((err) => { console.error('Error whilst getting cached profile for GroupTile', err); }); - }, + } - onMouseDown: function(e) { + onMouseDown = e => { e.preventDefault(); dis.dispatch({ action: 'view_group', group_id: this.props.groupId, }); - }, + }; - render: function() { + render() { const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const profile = this.state.profile || {}; @@ -135,7 +126,7 @@ const GroupTile = createReactClass({
{ this.props.groupId }
; - }, -}); + } +} export default GroupTile; diff --git a/src/components/views/groups/GroupUserSettings.js b/src/components/views/groups/GroupUserSettings.js index 8f57eccd058..9209106c8fe 100644 --- a/src/components/views/groups/GroupUserSettings.js +++ b/src/components/views/groups/GroupUserSettings.js @@ -15,33 +15,26 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -export default createReactClass({ - displayName: 'GroupUserSettings', +export default class GroupUserSettings extends React.Component { + static contextType = MatrixClientContext; - statics: { - contextType: MatrixClientContext, - }, + state = { + error: null, + groups: null, + }; - getInitialState() { - return { - error: null, - groups: null, - }; - }, - - componentDidMount: function() { + componentDidMount() { this.context.getJoinedGroups().then((result) => { this.setState({groups: result.groups || [], error: null}); }, (err) => { console.error(err); this.setState({groups: null, error: err}); }); - }, + } render() { let text = ""; @@ -70,5 +63,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index 2f2521f02dd..cc140a43526 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -17,7 +17,6 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import filesize from 'filesize'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import * as sdk from '../../../index'; @@ -117,16 +116,8 @@ function computedStyle(element) { return cssText; } -export default createReactClass({ - displayName: 'MFileBody', - - getInitialState: function() { - return { - decryptedBlob: (this.props.decryptedBlob ? this.props.decryptedBlob : null), - }; - }, - - propTypes: { +export default class MFileBody extends React.Component { + static propTypes = { /* the MatrixEvent to show */ mxEvent: PropTypes.object.isRequired, /* already decrypted blob */ @@ -135,7 +126,19 @@ export default createReactClass({ onHeightChanged: PropTypes.func, /* the shape of the tile, used */ 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 @@ -144,7 +147,7 @@ export default createReactClass({ * @params {Object} content The "content" key of the matrix event. * @return {string} the human readable link text for the attachment. */ - presentableTextForFile: function(content) { + presentableTextForFile(content) { let linkText = _t("Attachment"); if (content.body && content.body.length > 0) { // The content body should be the name of the file including a @@ -163,40 +166,33 @@ export default createReactClass({ linkText += ' (' + filesize(content.info.size) + ')'; } return linkText; - }, + } - _getContentUrl: function() { + _getContentUrl() { const content = this.props.mxEvent.getContent(); return MatrixClientPeg.get().mxcUrlToHttp(content.url); - }, - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { - this._iframe = createRef(); - this._dummyLink = createRef(); - this._downloadImage = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { // Add this to the list of mounted components to receive notifications // when the tint changes. this.id = nextMountId++; mounts[this.id] = this; this.tint(); - }, + } - componentDidUpdate: function(prevProps, prevState) { + componentDidUpdate(prevProps, prevState) { if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) { this.props.onHeightChanged(); } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { // Remove this from the list of mounted components delete mounts[this.id]; - }, + } - tint: function() { + tint = () => { // Update our tinted copy of require("../../../../res/img/download.svg") if (this._downloadImage.current) { this._downloadImage.current.src = tintedDownloadImageURL; @@ -210,9 +206,9 @@ export default createReactClass({ style: computedStyle(this._dummyLink.current), }, "*"); } - }, + }; - render: function() { + render() { const content = this.props.mxEvent.getContent(); const text = this.presentableTextForFile(content); const isEncrypted = content.file !== undefined; @@ -378,5 +374,5 @@ export default createReactClass({ { _t("Invalid file%(extra)s", { extra: extra }) } ; } - }, -}); + } +} diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js index fdc04deffce..86bf41699b1 100644 --- a/src/components/views/messages/MVideoBody.js +++ b/src/components/views/messages/MVideoBody.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import MFileBody from './MFileBody'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { decryptFile } from '../../../utils/DecryptFile'; @@ -25,27 +24,23 @@ import { _t } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; import InlineSpinner from '../elements/InlineSpinner'; -export default createReactClass({ - displayName: 'MVideoBody', - - propTypes: { +export default class MVideoBody extends React.Component { + static propTypes = { /* the MatrixEvent to show */ mxEvent: PropTypes.object.isRequired, /* called when the video has loaded */ onHeightChanged: PropTypes.func.isRequired, - }, - - getInitialState: function() { - return { - decryptedUrl: null, - decryptedThumbnailUrl: null, - decryptedBlob: null, - error: null, - }; - }, - - thumbScale: function(fullWidth, fullHeight, thumbWidth, thumbHeight) { + }; + + state = { + decryptedUrl: null, + decryptedThumbnailUrl: null, + decryptedBlob: null, + error: null, + }; + + thumbScale(fullWidth, fullHeight, thumbWidth, thumbHeight) { if (!fullWidth || !fullHeight) { // Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even // 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 return heightMulti; } - }, + } - _getContentUrl: function() { + _getContentUrl() { const content = this.props.mxEvent.getContent(); if (content.file !== undefined) { return this.state.decryptedUrl; } else { return MatrixClientPeg.get().mxcUrlToHttp(content.url); } - }, + } - _getThumbUrl: function() { + _getThumbUrl() { const content = this.props.mxEvent.getContent(); if (content.file !== undefined) { return this.state.decryptedThumbnailUrl; @@ -84,9 +79,9 @@ export default createReactClass({ } else { return null; } - }, + } - componentDidMount: function() { + componentDidMount() { const content = this.props.mxEvent.getContent(); if (content.file !== undefined && this.state.decryptedUrl === null) { let thumbnailPromise = Promise.resolve(null); @@ -118,18 +113,18 @@ export default createReactClass({ }); }); } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { if (this.state.decryptedUrl) { URL.revokeObjectURL(this.state.decryptedUrl); } if (this.state.decryptedThumbnailUrl) { URL.revokeObjectURL(this.state.decryptedThumbnailUrl); } - }, + } - render: function() { + render() { const content = this.props.mxEvent.getContent(); if (this.state.error !== null) { @@ -182,5 +177,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/messages/MessageEvent.js b/src/components/views/messages/MessageEvent.js index c3e5af2eb6f..f93813fe793 100644 --- a/src/components/views/messages/MessageEvent.js +++ b/src/components/views/messages/MessageEvent.js @@ -16,17 +16,14 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import SettingsStore from "../../../settings/SettingsStore"; import {Mjolnir} from "../../../mjolnir/Mjolnir"; import RedactedBody from "./RedactedBody"; import UnknownBody from "./UnknownBody"; -export default createReactClass({ - displayName: 'MessageEvent', - - propTypes: { +export default class MessageEvent extends React.Component { + static propTypes = { /* the MatrixEvent to show */ mxEvent: PropTypes.object.isRequired, @@ -47,22 +44,23 @@ export default createReactClass({ /* the maximum image height to use, if the event is an image */ 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(); - }, + } - getEventTileOps: function() { + getEventTileOps = () => { return this._body.current && this._body.current.getEventTileOps ? this._body.current.getEventTileOps() : null; - }, + }; - onTileUpdate: function() { + onTileUpdate = () => { this.forceUpdate(); - }, + }; - render: function() { + render() { const bodyTypes = { 'm.text': sdk.getComponent('messages.TextualBody'), 'm.notice': sdk.getComponent('messages.TextualBody'), @@ -123,5 +121,5 @@ export default createReactClass({ onHeightChanged={this.props.onHeightChanged} onMessageAllowed={this.onTileUpdate} />; - }, -}); + } +} diff --git a/src/components/views/messages/RoomAvatarEvent.js b/src/components/views/messages/RoomAvatarEvent.js index 78df6aa4a8c..f526d080ccc 100644 --- a/src/components/views/messages/RoomAvatarEvent.js +++ b/src/components/views/messages/RoomAvatarEvent.js @@ -18,22 +18,19 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import Modal from '../../../Modal'; import AccessibleButton from '../elements/AccessibleButton'; -export default createReactClass({ - displayName: 'RoomAvatarEvent', - - propTypes: { +export default class RoomAvatarEvent extends React.Component { + static propTypes = { /* the MatrixEvent to show */ mxEvent: PropTypes.object.isRequired, - }, + }; - onAvatarClick: function() { + onAvatarClick = () => { const cli = MatrixClientPeg.get(); const ev = this.props.mxEvent; const httpUrl = cli.mxcUrlToHttp(ev.getContent().url); @@ -50,9 +47,9 @@ export default createReactClass({ name: text, }; Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); - }, + }; - render: function() { + render() { const ev = this.props.mxEvent; const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); @@ -86,5 +83,5 @@ export default createReactClass({ } ); - }, -}); + } +} diff --git a/src/components/views/messages/RoomCreate.js b/src/components/views/messages/RoomCreate.js index 95bc460636b..22d5ebba1e4 100644 --- a/src/components/views/messages/RoomCreate.js +++ b/src/components/views/messages/RoomCreate.js @@ -17,22 +17,19 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import dis from '../../../dispatcher/dispatcher'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; -export default createReactClass({ - displayName: 'RoomCreate', - - propTypes: { +export default class RoomCreate extends React.Component { + static propTypes = { /* the MatrixEvent to show */ mxEvent: PropTypes.object.isRequired, - }, + }; - _onLinkClicked: function(e) { + _onLinkClicked = e => { e.preventDefault(); const predecessor = this.props.mxEvent.getContent()['predecessor']; @@ -43,9 +40,9 @@ export default createReactClass({ highlighted: true, room_id: predecessor['room_id'], }); - }, + }; - render: function() { + render() { const predecessor = this.props.mxEvent.getContent()['predecessor']; if (predecessor === undefined) { return
; // We should never have been instaniated in this case @@ -66,5 +63,5 @@ export default createReactClass({ {_t("Click here to see older messages.")}
; - }, -}); + } +} diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index 5064c4d2b24..afe2d6d1189 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -16,31 +16,25 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import Flair from '../elements/Flair.js'; import FlairStore from '../../../stores/FlairStore'; import { _t } from '../../../languageHandler'; import {getUserNameColorClass} from '../../../utils/FormattingUtils'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -export default createReactClass({ - displayName: 'SenderProfile', - propTypes: { +export default class SenderProfile extends React.Component { + static propTypes = { mxEvent: PropTypes.object.isRequired, // event whose sender we're showing text: PropTypes.string, // Text to show. Defaults to sender name onClick: PropTypes.func, - }, + }; - statics: { - contextType: MatrixClientContext, - }, + static contextType = MatrixClientContext; - getInitialState() { - return { - userGroups: null, - relatedGroups: [], - }; - }, + state = { + userGroups: null, + relatedGroups: [], + }; componentDidMount() { this.unmounted = false; @@ -54,20 +48,20 @@ export default createReactClass({ }); this.context.on('RoomState.events', this.onRoomStateEvents); - }, + } componentWillUnmount() { this.unmounted = true; this.context.removeListener('RoomState.events', this.onRoomStateEvents); - }, + } - onRoomStateEvents(event) { + onRoomStateEvents = event => { if (event.getType() === 'm.room.related_groups' && event.getRoomId() === this.props.mxEvent.getRoomId() ) { this._updateRelatedGroups(); } - }, + }; _updateRelatedGroups() { if (this.unmounted) return; @@ -78,7 +72,7 @@ export default createReactClass({ this.setState({ relatedGroups: relatedGroupsEvent ? relatedGroupsEvent.getContent().groups || [] : [], }); - }, + } _getDisplayedGroups(userGroups, relatedGroups) { let displayedGroups = userGroups || []; @@ -90,7 +84,7 @@ export default createReactClass({ displayedGroups = []; } return displayedGroups; - }, + } render() { const {mxEvent} = this.props; @@ -138,5 +132,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index df72398b1cd..e83262ce697 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -19,7 +19,6 @@ limitations under the License. import React, {createRef} from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import highlight from 'highlight.js'; import * as HtmlUtils from '../../../HtmlUtils'; import {formatDate} from '../../../DateUtils'; @@ -37,10 +36,8 @@ import {toRightOf} from "../../structures/ContextMenu"; import {copyPlaintext} from "../../../utils/strings"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -export default createReactClass({ - displayName: 'TextualBody', - - propTypes: { +export default class TextualBody extends React.Component { + static propTypes = { /* the MatrixEvent to show */ mxEvent: PropTypes.object.isRequired, @@ -58,10 +55,14 @@ export default createReactClass({ /* the shape of the tile, used */ tileShape: PropTypes.string, - }, + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this._content = createRef(); + + this.state = { // the URLs (if any) to be previewed with a LinkPreviewWidget // inside this TextualBody. links: [], @@ -69,20 +70,15 @@ export default createReactClass({ // track whether the preview widget is hidden widgetHidden: false, }; - }, - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { - this._content = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { this._unmounted = false; this._pills = []; if (!this.props.editState) { this._applyFormatting(); } - }, + } _applyFormatting() { this.activateSpoilers([this._content.current]); @@ -119,9 +115,9 @@ export default createReactClass({ } this._addCodeCopyButton(); } - }, + } - componentDidUpdate: function(prevProps) { + componentDidUpdate(prevProps) { if (!this.props.editState) { const stoppedEditing = prevProps.editState && !this.props.editState; const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId; @@ -129,14 +125,14 @@ export default createReactClass({ this._applyFormatting(); } } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this._unmounted = true; 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); // exploit that events are immutable :) @@ -148,9 +144,9 @@ export default createReactClass({ nextProps.editState !== this.props.editState || nextState.links !== this.state.links || nextState.widgetHidden !== this.state.widgetHidden); - }, + } - calculateUrlPreview: function() { + calculateUrlPreview() { //console.info("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview); if (this.props.showUrlPreview) { @@ -176,9 +172,9 @@ export default createReactClass({ this.setState({ links: [] }); } } - }, + } - activateSpoilers: function(nodes) { + activateSpoilers(nodes) { let node = nodes[0]; while (node) { if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") { @@ -204,9 +200,9 @@ export default createReactClass({ node = node.nextSibling; } - }, + } - findLinks: function(nodes) { + findLinks(nodes) { let links = []; for (let i = 0; i < nodes.length; i++) { @@ -223,9 +219,9 @@ export default createReactClass({ } } return links; - }, + } - isLinkPreviewable: function(node) { + isLinkPreviewable(node) { // don't try to preview relative links if (!node.getAttribute("href").startsWith("http://") && !node.getAttribute("href").startsWith("https://")) { @@ -256,7 +252,7 @@ export default createReactClass({ return true; } } - }, + } _addCodeCopyButton() { // Add 'copy' buttons to pre blocks @@ -288,41 +284,39 @@ export default createReactClass({ div.appendChild(p); div.appendChild(button); }); - }, + } - onCancelClick: function(event) { + onCancelClick = event => { this.setState({ widgetHidden: true }); // FIXME: persist this somewhere smarter than local storage if (global.localStorage) { global.localStorage.setItem("hide_preview_" + this.props.mxEvent.getId(), "1"); } this.forceUpdate(); - }, + }; - onEmoteSenderClick: function(event) { + onEmoteSenderClick = event => { const mxEvent = this.props.mxEvent; dis.dispatch({ action: 'insert_mention', user_id: mxEvent.getSender(), }); - }, + }; - getEventTileOps: function() { - return { - isWidgetHidden: () => { - return this.state.widgetHidden; - }, + getEventTileOps = () => ({ + isWidgetHidden: () => { + return this.state.widgetHidden; + }, - unhideWidget: () => { - this.setState({ widgetHidden: false }); - if (global.localStorage) { - global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId()); - } - }, - }; - }, + unhideWidget: () => { + this.setState({widgetHidden: false}); + if (global.localStorage) { + global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId()); + } + }, + }); - onStarterLinkClick: function(starterLink, ev) { + onStarterLinkClick = (starterLink, ev) => { ev.preventDefault(); // 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 @@ -353,7 +347,7 @@ export default createReactClass({ "Do you wish to continue?", { integrationsUrl: integrationsUrl }) } , button: _t("Continue"), - onFinished: function(confirmed) { + onFinished(confirmed) { if (!confirmed) { return; } @@ -367,14 +361,14 @@ export default createReactClass({ }, }); }); - }, + }; - _openHistoryDialog: async function() { + _openHistoryDialog = async () => { const MessageEditHistoryDialog = sdk.getComponent("views.dialogs.MessageEditHistoryDialog"); Modal.createDialog(MessageEditHistoryDialog, {mxEvent: this.props.mxEvent}); - }, + }; - _renderEditedMarker: function() { + _renderEditedMarker() { const date = this.props.mxEvent.replacingEventDate(); const dateString = date && formatDate(date); @@ -397,9 +391,9 @@ export default createReactClass({ {`(${_t("edited")})`} ); - }, + } - render: function() { + render() { if (this.props.editState) { const EditMessageComposer = sdk.getComponent('rooms.EditMessageComposer'); return ; @@ -468,5 +462,5 @@ export default createReactClass({ ); } - }, -}); + } +} diff --git a/src/components/views/messages/TextualEvent.js b/src/components/views/messages/TextualEvent.js index 1f48516f750..99e94147f7c 100644 --- a/src/components/views/messages/TextualEvent.js +++ b/src/components/views/messages/TextualEvent.js @@ -17,22 +17,19 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as TextForEvent from "../../../TextForEvent"; -export default createReactClass({ - displayName: 'TextualEvent', - - propTypes: { +export default class TextualEvent extends React.Component { + static propTypes = { /* the MatrixEvent to show */ mxEvent: PropTypes.object.isRequired, - }, + }; - render: function() { + render() { const text = TextForEvent.textForEvent(this.props.mxEvent); if (text == null || text.length === 0) return null; return (
{ text }
); - }, -}); + } +} diff --git a/src/components/views/room_settings/UrlPreviewSettings.js b/src/components/views/room_settings/UrlPreviewSettings.js index fa5c1baf8f9..114e9b28949 100644 --- a/src/components/views/room_settings/UrlPreviewSettings.js +++ b/src/components/views/room_settings/UrlPreviewSettings.js @@ -19,7 +19,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from "../../../index"; import { _t, _td } from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; @@ -29,20 +28,18 @@ import {Action} from "../../../dispatcher/actions"; import {SettingLevel} from "../../../settings/SettingLevel"; -export default createReactClass({ - displayName: 'UrlPreviewSettings', - - propTypes: { +export default class UrlPreviewSettings extends React.Component { + static propTypes = { room: PropTypes.object, - }, + }; - _onClickUserSettings: (e) => { + _onClickUserSettings = (e) => { e.preventDefault(); e.stopPropagation(); dis.fire(Action.ViewUserSettings); - }, + }; - render: function() { + render() { const SettingsFlag = sdk.getComponent("elements.SettingsFlag"); const roomId = this.props.room.roomId; const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId); @@ -110,5 +107,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index 8cf7a54da20..ed76e9137af 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import AppTile from '../elements/AppTile'; 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 const MAX_WIDGETS = 2; -export default createReactClass({ - displayName: 'AppsDrawer', - - propTypes: { +export default class AppsDrawer extends React.Component { + static propTypes = { userId: PropTypes.string.isRequired, room: PropTypes.object.isRequired, showApps: PropTypes.bool, // Should apps be rendered hide: PropTypes.bool, // If rendered, should apps drawer be visible - }, + }; - getDefaultProps: () => ({ + static defaultProps = { showApps: true, hide: false, - }), + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this.state = { apps: this._getApps(), }; - }, + } - componentDidMount: function() { + componentDidMount() { ScalarMessaging.startListening(); MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); WidgetEchoStore.on('update', this._updateApps); this.dispatcherRef = dis.register(this.onAction); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { ScalarMessaging.stopListening(); if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); } WidgetEchoStore.removeListener('update', this._updateApps); if (this.dispatcherRef) dis.unregister(this.dispatcherRef); - }, + } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event UNSAFE_componentWillReceiveProps(newProps) { // Room has changed probably, update apps this._updateApps(); - }, + } - onAction: function(action) { + onAction = (action) => { const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer'; switch (action.action) { case 'appsDrawer': @@ -93,16 +92,16 @@ export default createReactClass({ break; } - }, + }; - onRoomStateEvents: function(ev, state) { + onRoomStateEvents = (ev, state) => { if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') { return; } this._updateApps(); - }, + }; - _getApps: function() { + _getApps() { const widgets = WidgetEchoStore.getEchoedRoomWidgets( 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(), ); }); - }, + } - _updateApps: function() { + _updateApps = () => { const apps = this._getApps(); this.setState({ apps: apps, }); - }, + }; - _canUserModify: function() { + _canUserModify() { try { return WidgetUtils.canUserModifyWidgets(this.props.room.roomId); } catch (err) { console.error(err); return false; } - }, + } - _launchManageIntegrations: function() { + _launchManageIntegrations() { if (SettingsStore.getValue("feature_many_integration_managers")) { IntegrationManagers.sharedInstance().openAll(); } else { IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ'); } - }, + } - onClickAddWidget: function(e) { + onClickAddWidget = (e) => { e.preventDefault(); // Display a warning dialog if the max number of widgets have already been added to the room const apps = this._getApps(); @@ -152,9 +151,9 @@ export default createReactClass({ return; } this._launchManageIntegrations(); - }, + }; - render: function() { + render() { const apps = this.state.apps.map((app, index, arr) => { const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId); @@ -211,5 +210,5 @@ export default createReactClass({ { this._canUserModify() && addWidget } ); - }, -}); + } +} diff --git a/src/components/views/rooms/AuxPanel.js b/src/components/views/rooms/AuxPanel.js index 521aeec4067..fc31d66160c 100644 --- a/src/components/views/rooms/AuxPanel.js +++ b/src/components/views/rooms/AuxPanel.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import * as sdk from '../../../index'; import dis from "../../../dispatcher/dispatcher"; @@ -31,10 +30,8 @@ import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import CallView from "../voip/CallView"; -export default createReactClass({ - displayName: 'AuxPanel', - - propTypes: { +export default class AuxPanel extends React.Component { + static propTypes = { // js-sdk room object room: PropTypes.object.isRequired, userId: PropTypes.string.isRequired, @@ -58,42 +55,46 @@ export default createReactClass({ // content in a way that is likely to make it change size. onResize: PropTypes.func, fullHeight: PropTypes.bool, - }, + }; - getDefaultProps: () => ({ + static defaultProps = { showApps: true, hideAppsDrawer: false, - }), + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { counters: this._computeCounters() }; - }, + this.state = { + counters: this._computeCounters(), + }; + } - componentDidMount: function() { + componentDidMount() { const cli = MatrixClientPeg.get(); cli.on("RoomState.events", this._rateLimitedUpdate); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { const cli = MatrixClientPeg.get(); if (cli) { cli.removeListener("RoomState.events", this._rateLimitedUpdate); } - }, + } - shouldComponentUpdate: function(nextProps, nextState) { + shouldComponentUpdate(nextProps, nextState) { return (!ObjectUtils.shallowEqual(this.props, nextProps) || !ObjectUtils.shallowEqual(this.state, nextState)); - }, + } - componentDidUpdate: function(prevProps, prevState) { + componentDidUpdate(prevProps, prevState) { // most changes are likely to cause a resize if (this.props.onResize) { this.props.onResize(); } - }, + } - onConferenceNotificationClick: function(ev, type) { + onConferenceNotificationClick = (ev, type) => { dis.dispatch({ action: 'place_call', type: type, @@ -101,15 +102,15 @@ export default createReactClass({ }); ev.stopPropagation(); ev.preventDefault(); - }, + }; - _rateLimitedUpdate: new RateLimitedFunc(function() { + _rateLimitedUpdate = new RateLimitedFunc(() => { if (SettingsStore.getValue("feature_state_counters")) { this.setState({counters: this._computeCounters()}); } - }, 500), + }, 500); - _computeCounters: function() { + _computeCounters() { let counters = []; if (this.props.room && SettingsStore.getValue("feature_state_counters")) { @@ -140,9 +141,9 @@ export default createReactClass({ } return counters; - }, + } - render: function() { + render() { const TintableSvg = sdk.getComponent("elements.TintableSvg"); let fileDropTarget = null; @@ -274,5 +275,5 @@ export default createReactClass({ { this.props.children } ); - }, -}); + } +} diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js index d880e824da7..9017e4aa3e9 100644 --- a/src/components/views/rooms/EntityTile.js +++ b/src/components/views/rooms/EntityTile.js @@ -18,7 +18,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import AccessibleButton from '../elements/AccessibleButton'; import { _t } from '../../../languageHandler'; @@ -51,10 +50,8 @@ function presenceClassForMember(presenceState, lastActiveAgo, showPresence) { } } -const EntityTile = createReactClass({ - displayName: 'EntityTile', - - propTypes: { +class EntityTile extends React.Component { + static propTypes = { name: PropTypes.string, title: PropTypes.string, avatarJsx: PropTypes.any, // @@ -70,33 +67,29 @@ const EntityTile = createReactClass({ showPresence: PropTypes.bool, subtextLabel: PropTypes.string, e2eStatus: PropTypes.string, - }, - - getDefaultProps: function() { - return { - shouldComponentUpdate: function(nextProps, nextState) { return true; }, - onClick: function() {}, - presenceState: "offline", - presenceLastActiveAgo: 0, - presenceLastTs: 0, - showInviteButton: false, - suppressOnHover: false, - showPresence: true, - }; - }, - - getInitialState: function() { - return { - hover: false, - }; - }, - - shouldComponentUpdate: function(nextProps, nextState) { + }; + + static defaultProps = { + shouldComponentUpdate: function(nextProps, nextState) { return true; }, + onClick: function() {}, + presenceState: "offline", + presenceLastActiveAgo: 0, + presenceLastTs: 0, + showInviteButton: false, + suppressOnHover: false, + showPresence: true, + }; + + state = { + hover: false, + }; + + shouldComponentUpdate(nextProps, nextState) { if (this.state.hover !== nextState.hover) return true; return this.props.shouldComponentUpdate(nextProps, nextState); - }, + } - render: function() { + render() { const mainClassNames = { "mx_EntityTile": true, "mx_EntityTile_noHover": this.props.suppressOnHover, @@ -193,8 +186,8 @@ const EntityTile = createReactClass({ ); - }, -}); + } +} EntityTile.POWER_STATUS_MODERATOR = "moderator"; EntityTile.POWER_STATUS_ADMIN = "admin"; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 2f004472876..d1738301d92 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -20,7 +20,6 @@ limitations under the License. import ReplyThread from "../elements/ReplyThread"; import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import classNames from "classnames"; import { _t, _td } from '../../../languageHandler'; import * as TextForEvent from "../../../TextForEvent"; @@ -127,10 +126,8 @@ const MAX_READ_AVATARS = 5; // | '--------------------------------------' | // '----------------------------------------------------------' -export default createReactClass({ - displayName: 'EventTile', - - propTypes: { +export default class EventTile extends React.Component { + static propTypes = { /* the MatrixEvent to show */ mxEvent: PropTypes.object.isRequired, @@ -209,17 +206,19 @@ export default createReactClass({ // whether to use the irc layout useIRCLayout: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - // no-op function because onHeightChanged is optional yet some sub-components assume its existence - onHeightChanged: function() {}, - }; - }, + static defaultProps = { + // no-op function because onHeightChanged is optional yet some sub-components assume its existence + onHeightChanged: function() {}, + }; + + static contextType = MatrixClientContext; - getInitialState: function() { - return { + constructor(props) { + super(props); + + this.state = { // Whether the action bar is focused. actionBarFocused: false, // 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` 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 this._suppressReadReceiptAnimation = true; this._verifyEvent(this.props.mxEvent); this._tile = createRef(); this._replyThread = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { this._suppressReadReceiptAnimation = false; const client = this.context; client.on("deviceVerificationChanged", this.onDeviceVerificationChanged); @@ -257,26 +249,26 @@ export default createReactClass({ if (this.props.showReactions) { this.props.mxEvent.on("Event.relationsCreated", this._onReactionsCreated); } - }, + } // 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 // the send process. if (nextProps.eventSendStatus !== this.props.eventSendStatus) { this._verifyEvent(nextProps.mxEvent); } - }, + } - shouldComponentUpdate: function(nextProps, nextState) { + shouldComponentUpdate(nextProps, nextState) { if (!ObjectUtils.shallowEqual(this.state, nextState)) { return true; } return !this._propsEqual(this.props, nextProps); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { const client = this.context; client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); @@ -284,31 +276,31 @@ export default createReactClass({ if (this.props.showReactions) { this.props.mxEvent.removeListener("Event.relationsCreated", this._onReactionsCreated); } - }, + } /** called when the event is decrypted after we show it. */ - _onDecrypted: function() { + _onDecrypted = () => { // we need to re-verify the sending device. // (we call onHeightChanged in _verifyEvent to handle the case where decryption // has caused a change in size of the event tile) this._verifyEvent(this.props.mxEvent); this.forceUpdate(); - }, + }; - onDeviceVerificationChanged: function(userId, device) { + onDeviceVerificationChanged = (userId, device) => { if (userId === this.props.mxEvent.getSender()) { this._verifyEvent(this.props.mxEvent); } - }, + }; - onUserVerificationChanged: function(userId, _trustStatus) { + onUserVerificationChanged = (userId, _trustStatus) => { if (userId === this.props.mxEvent.getSender()) { this._verifyEvent(this.props.mxEvent); } - }, + }; - _verifyEvent: async function(mxEvent) { + async _verifyEvent(mxEvent) { if (!mxEvent.isEncrypted()) { return; } @@ -360,9 +352,9 @@ export default createReactClass({ this.setState({ verified: E2E_STATE.VERIFIED, }, this.props.onHeightChanged); // Decryption may have caused a change in size - }, + } - _propsEqual: function(objA, objB) { + _propsEqual(objA, objB) { const keysA = Object.keys(objA); const keysB = Object.keys(objB); @@ -408,9 +400,9 @@ export default createReactClass({ } } return true; - }, + } - shouldHighlight: function() { + shouldHighlight() { const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent); if (!actions || !actions.tweaks) { return false; } @@ -420,15 +412,15 @@ export default createReactClass({ } return actions.tweaks.highlight; - }, + } - toggleAllReadAvatars: function() { + toggleAllReadAvatars = () => { this.setState({ allReadAvatars: !this.state.allReadAvatars, }); - }, + }; - getReadAvatars: function() { + getReadAvatars() { // return early if there are no read receipts if (!this.props.readReceipts || this.props.readReceipts.length === 0) { return (); @@ -494,17 +486,17 @@ export default createReactClass({ { remText } { avatars } ; - }, + } - onSenderProfileClick: function(event) { + onSenderProfileClick = event => { const mxEvent = this.props.mxEvent; dis.dispatch({ action: 'insert_mention', user_id: mxEvent.getSender(), }); - }, + }; - onRequestKeysClick: function() { + onRequestKeysClick = () => { this.setState({ // Indicate in the UI that the keys have been requested (this is expected to // 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 // decrypted successfully. 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 // matrix.to, but also for it to enable routing within Element when clicked. e.preventDefault(); @@ -527,9 +519,9 @@ export default createReactClass({ highlighted: true, room_id: this.props.mxEvent.getRoomId(), }); - }, + }; - _renderE2EPadlock: function() { + _renderE2EPadlock() { const ev = this.props.mxEvent; // event could not be decrypted @@ -570,23 +562,19 @@ export default createReactClass({ // no padlock needed return null; - }, + } - onActionBarFocusChange(focused) { + onActionBarFocusChange = focused => { this.setState({ actionBarFocused: focused, }); - }, + }; - getTile() { - return this._tile.current; - }, + getTile = () => this._tile.current; - getReplyThread() { - return this._replyThread.current; - }, + getReplyThread = () => this._replyThread.current; - getReactions() { + getReactions = () => { if ( !this.props.showReactions || !this.props.getRelationsForEvent @@ -602,9 +590,9 @@ export default createReactClass({ console.trace("Stacktrace for https://github.com/vector-im/element-web/issues/11120"); } return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction"); - }, + }; - _onReactionsCreated(relationType, eventType) { + _onReactionsCreated = (relationType, eventType) => { if (relationType !== "m.annotation" || eventType !== "m.reaction") { return; } @@ -612,9 +600,9 @@ export default createReactClass({ this.setState({ reactions: this.getReactions(), }); - }, + }; - render: function() { + render() { const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp'); const SenderProfile = sdk.getComponent('messages.SenderProfile'); 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 const messageTypes = ['m.room.message', 'm.sticker']; diff --git a/src/components/views/rooms/ForwardMessage.js b/src/components/views/rooms/ForwardMessage.js index 03ca32c5e63..8daea8dc17c 100644 --- a/src/components/views/rooms/ForwardMessage.js +++ b/src/components/views/rooms/ForwardMessage.js @@ -17,49 +17,46 @@ import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher/dispatcher'; import {Key} from '../../../Keyboard'; -export default createReactClass({ - displayName: 'ForwardMessage', - - propTypes: { +export default class ForwardMessage extends React.Component { + static propTypes = { onCancelClick: PropTypes.func.isRequired, - }, + }; - componentDidMount: function() { + componentDidMount() { dis.dispatch({ action: 'panel_disable', middleDisabled: true, }); document.addEventListener('keydown', this._onKeyDown); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { dis.dispatch({ action: 'panel_disable', middleDisabled: false, }); document.removeEventListener('keydown', this._onKeyDown); - }, + } - _onKeyDown: function(ev) { + _onKeyDown = ev => { switch (ev.key) { case Key.ESCAPE: this.props.onCancelClick(); break; } - }, + }; - render: function() { + render() { return (

{ _t('Please select the destination room for this message') }

); - }, -}); + } +} diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js index d1c1ce04f9e..a3d59e5137c 100644 --- a/src/components/views/rooms/LinkPreviewWidget.js +++ b/src/components/views/rooms/LinkPreviewWidget.js @@ -17,7 +17,6 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { AllHtmlEntities } from 'html-entities'; import {linkifyElement} from '../../../HtmlUtils'; import SettingsStore from "../../../settings/SettingsStore"; @@ -27,24 +26,21 @@ import Modal from "../../../Modal"; import * as ImageUtils from "../../../ImageUtils"; import { _t } from "../../../languageHandler"; -export default createReactClass({ - displayName: 'LinkPreviewWidget', - - propTypes: { +export default class LinkPreviewWidget extends React.Component { + static propTypes = { link: PropTypes.string.isRequired, // the URL being previewed mxEvent: PropTypes.object.isRequired, // the Event associated with the preview onCancelClick: PropTypes.func, // called when the preview's cancel ('hide') button is clicked onHeightChanged: PropTypes.func, // called when the preview's contents has loaded - }, + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this.state = { preview: null, }; - }, - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this.unmounted = false; MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.mxEvent.getTs()).then((res)=>{ if (this.unmounted) { @@ -59,25 +55,25 @@ export default createReactClass({ }); this._description = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { if (this._description.current) { linkifyElement(this._description.current); } - }, + } - componentDidUpdate: function() { + componentDidUpdate() { if (this._description.current) { linkifyElement(this._description.current); } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this.unmounted = true; - }, + } - onImageClick: function(ev) { + onImageClick = ev => { const p = this.state.preview; if (ev.button != 0 || ev.metaKey) return; ev.preventDefault(); @@ -98,9 +94,9 @@ export default createReactClass({ }; Modal.createDialog(ImageView, params, "mx_Dialog_lightbox"); - }, + }; - render: function() { + render() { const p = this.state.preview; if (!p || Object.keys(p).length === 0) { return
; @@ -149,5 +145,5 @@ export default createReactClass({
); - }, -}); + } +} diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index e2d7e3f8e01..920e3cf691b 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -17,7 +17,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import dis from '../../../dispatcher/dispatcher'; @@ -36,23 +35,19 @@ const SHOW_MORE_INCREMENT = 100; // matches all ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ const SORT_REGEX = /[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/g; -export default createReactClass({ - displayName: 'MemberList', +export default class MemberList extends React.Component { + constructor(props) { + super(props); - getInitialState: function() { const cli = MatrixClientPeg.get(); if (cli.hasLazyLoadMembersEnabled()) { // show an empty list - return this._getMembersState([]); + this.state = this._getMembersState([]); } 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; - const cli = MatrixClientPeg.get(); if (cli.hasLazyLoadMembersEnabled()) { this._showMembersAccordingToMembershipWithLL(); cli.on("Room.myMembership", this.onMyMembership); @@ -66,9 +61,9 @@ export default createReactClass({ if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) { this._showPresence = enablePresenceByHsUrl[hsUrl]; } - }, + } - _listenForMembersChanges: function() { + _listenForMembersChanges() { const cli = MatrixClientPeg.get(); cli.on("RoomState.members", this.onRoomStateMember); cli.on("RoomMember.name", this.onRoomMemberName); @@ -80,9 +75,9 @@ export default createReactClass({ cli.on("User.presence", this.onUserPresenceChange); cli.on("User.currentlyActive", this.onUserPresenceChange); // cli.on("Room.timeline", this.onRoomTimeline); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this._mounted = false; const cli = MatrixClientPeg.get(); if (cli) { @@ -98,14 +93,14 @@ export default createReactClass({ // cancel any pending calls to the rate_limited_funcs this._updateList.cancelPendingCall(); - }, + } /** * If lazy loading is enabled, either: * show a spinner and load the members if the user is joined, * or show the members available so far if the user is invited */ - _showMembersAccordingToMembershipWithLL: async function() { + async _showMembersAccordingToMembershipWithLL() { const cli = MatrixClientPeg.get(); if (cli.hasLazyLoadMembersEnabled()) { const cli = MatrixClientPeg.get(); @@ -125,9 +120,9 @@ export default createReactClass({ this.setState(this._getMembersState(this.roomMembers())); } } - }, + } - _getMembersState: function(members) { + _getMembersState(members) { // set the state after determining _showPresence to make sure it's // taken into account while rerendering return { @@ -142,9 +137,9 @@ export default createReactClass({ truncateAtInvited: INITIAL_LOAD_NUM_INVITED, searchQuery: "", }; - }, + } - onUserPresenceChange(event, user) { + onUserPresenceChange = (event, user) => { // Attach a SINGLE listener for global presence changes then locate the // member tile and re-render it. This is more efficient than every tile // ever attaching their own listener. @@ -153,9 +148,9 @@ export default createReactClass({ if (tile) { this._updateList(); // reorder the membership list } - }, + }; - onRoom: function(room) { + onRoom = room => { if (room.roomId !== this.props.roomId) { return; } @@ -163,40 +158,40 @@ export default createReactClass({ // we need to wait till the room is fully populated with state // before refreshing the member list else we get a stale list. this._showMembersAccordingToMembershipWithLL(); - }, + }; - onMyMembership: function(room, membership, oldMembership) { + onMyMembership = (room, membership, oldMembership) => { if (room.roomId === this.props.roomId && membership === "join") { this._showMembersAccordingToMembershipWithLL(); } - }, + }; - onRoomStateMember: function(ev, state, member) { + onRoomStateMember = (ev, state, member) => { if (member.roomId !== this.props.roomId) { return; } this._updateList(); - }, + }; - onRoomMemberName: function(ev, member) { + onRoomMemberName = (ev, member) => { if (member.roomId !== this.props.roomId) { return; } this._updateList(); - }, + }; - onRoomStateEvent: function(event, state) { + onRoomStateEvent = (event, state) => { if (event.getRoomId() === this.props.roomId && event.getType() === "m.room.third_party_invite") { this._updateList(); } - }, + }; - _updateList: rate_limited_func(function() { + _updateList = rate_limited_func(() => { this._updateListNow(); - }, 500), + }, 500); - _updateListNow: function() { + _updateListNow() { // console.log("Updating memberlist"); const newState = { loading: false, @@ -205,9 +200,9 @@ export default createReactClass({ newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join', this.state.searchQuery); newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite', this.state.searchQuery); this.setState(newState); - }, + } - getMembersWithUser: function() { + getMembersWithUser() { if (!this.props.roomId) return []; const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.roomId); @@ -228,9 +223,9 @@ export default createReactClass({ }); return allMembers; - }, + } - roomMembers: function() { + roomMembers() { const ConferenceHandler = CallHandler.getConferenceHandler(); const allMembers = this.getMembersWithUser(); @@ -244,17 +239,17 @@ export default createReactClass({ }); filteredAndSortedMembers.sort(this.memberSort); return filteredAndSortedMembers; - }, + } - _createOverflowTileJoined: function(overflowCount, totalCount) { + _createOverflowTileJoined(overflowCount, totalCount) { return this._createOverflowTile(overflowCount, totalCount, this._showMoreJoinedMemberList); - }, + } - _createOverflowTileInvited: function(overflowCount, totalCount) { + _createOverflowTileInvited(overflowCount, totalCount) { 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. const EntityTile = sdk.getComponent("rooms.EntityTile"); const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); @@ -265,33 +260,33 @@ export default createReactClass({ } name={text} presenceState="online" suppressOnHover={true} onClick={onClick} /> ); - }, + } - _showMoreJoinedMemberList: function() { + _showMoreJoinedMemberList = () => { this.setState({ truncateAtJoined: this.state.truncateAtJoined + SHOW_MORE_INCREMENT, }); - }, + }; - _showMoreInvitedMemberList: function() { + _showMoreInvitedMemberList = () => { this.setState({ truncateAtInvited: this.state.truncateAtInvited + SHOW_MORE_INCREMENT, }); - }, + }; - memberString: function(member) { + memberString(member) { if (!member) { return "(null)"; } else { const u = member.user; return "(" + member.name + ", " + member.powerLevel + ", " + (u ? u.lastActiveAgo : "") + ", " + (u ? u.getLastActiveTs() : "") + ", " + (u ? u.currentlyActive : "") + ", " + (u ? u.presence : "") + ")"; } - }, + } // returns negative if a comes before b, // returns 0 if a and b are equivalent in ordering // returns positive if a comes after b. - memberSort: function(memberA, memberB) { + memberSort = (memberA, memberB) => { // order by presence, with "active now" first. // ...and then by power level // ...and then by last active @@ -348,24 +343,24 @@ export default createReactClass({ ignorePunctuation: true, sensitivity: "base", }); - }, + }; - onSearchQueryChanged: function(searchQuery) { + onSearchQueryChanged = searchQuery => { this.setState({ searchQuery, filteredJoinedMembers: this._filterMembers(this.state.members, 'join', searchQuery), filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', searchQuery), }); - }, + }; - _onPending3pidInviteClick: function(inviteEvent) { + _onPending3pidInviteClick = inviteEvent => { dis.dispatch({ action: 'view_3pid_invite', event: inviteEvent, }); - }, + }; - _filterMembers: function(members, membership, query) { + _filterMembers(members, membership, query) { return members.filter((m) => { if (query) { query = query.toLowerCase(); @@ -379,9 +374,9 @@ export default createReactClass({ return m.membership === membership; }); - }, + } - _getPending3PidInvites: function() { + _getPending3PidInvites() { // include 3pid invites (m.room.third_party_invite) state events. // 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 @@ -399,9 +394,9 @@ export default createReactClass({ return true; }); } - }, + } - _makeMemberTiles: function(members) { + _makeMemberTiles(members) { const MemberTile = sdk.getComponent("rooms.MemberTile"); const EntityTile = sdk.getComponent("rooms.EntityTile"); @@ -415,30 +410,30 @@ export default createReactClass({ onClick={() => this._onPending3pidInviteClick(m)} />; } }); - }, + } - _getChildrenJoined: function(start, end) { + _getChildrenJoined(start, end) { return this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end)); - }, + } - _getChildCountJoined: function() { + _getChildCountJoined() { return this.state.filteredJoinedMembers.length; - }, + } - _getChildrenInvited: function(start, end) { + _getChildrenInvited(start, end) { let targets = this.state.filteredInvitedMembers; if (end > this.state.filteredInvitedMembers.length) { targets = targets.concat(this._getPending3PidInvites()); } return this._makeMemberTiles(targets.slice(start, end)); - }, + } - _getChildCountInvited: function() { + _getChildCountInvited() { return this.state.filteredInvitedMembers.length + (this._getPending3PidInvites() || []).length; - }, + } - render: function() { + render() { if (this.state.loading) { const Spinner = sdk.getComponent("elements.Spinner"); return
; @@ -501,9 +496,9 @@ export default createReactClass({ onSearch={ this.onSearchQueryChanged } /> ); - }, + } - onInviteButtonClick: function() { + onInviteButtonClick = () => { if (MatrixClientPeg.get().isGuest()) { dis.dispatch({action: 'require_registration'}); return; @@ -514,5 +509,5 @@ export default createReactClass({ action: 'view_invite', roomId: this.props.roomId, }); - }, -}); + }; +} diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js index ebb8b7999dd..a43b42b6d3f 100644 --- a/src/components/views/rooms/MemberTile.js +++ b/src/components/views/rooms/MemberTile.js @@ -18,34 +18,31 @@ limitations under the License. import SettingsStore from "../../../settings/SettingsStore"; import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from "../../../index"; import dis from "../../../dispatcher/dispatcher"; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import {Action} from "../../../dispatcher/actions"; -export default createReactClass({ - displayName: 'MemberTile', - - propTypes: { +export default class MemberTile extends React.Component { + static propTypes = { member: PropTypes.any.isRequired, // RoomMember showPresence: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - showPresence: true, - }; - }, + static defaultProps = { + showPresence: true, + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this.state = { statusMessage: this.getStatusMessage(), isRoomEncrypted: false, e2eStatus: null, }; - }, + } componentDidMount() { const cli = MatrixClientPeg.get(); @@ -72,7 +69,7 @@ export default createReactClass({ cli.on("RoomState.events", this.onRoomStateEvents); } } - }, + } componentWillUnmount() { const cli = MatrixClientPeg.get(); @@ -90,9 +87,9 @@ export default createReactClass({ cli.removeListener("userTrustStatusChanged", this.onUserTrustStatusChanged); cli.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); } - }, + } - onRoomStateEvents: function(ev) { + onRoomStateEvents = ev => { if (ev.getType() !== "m.room.encryption") return; const { roomId } = this.props.member; if (ev.getRoomId() !== roomId) return; @@ -104,19 +101,19 @@ export default createReactClass({ isRoomEncrypted: true, }); this.updateE2EStatus(); - }, + }; - onUserTrustStatusChanged: function(userId, trustStatus) { + onUserTrustStatusChanged = (userId, trustStatus) => { if (userId !== this.props.member.userId) return; this.updateE2EStatus(); - }, + }; - onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) { + onDeviceVerificationChanged = (userId, deviceId, deviceInfo) => { if (userId !== this.props.member.userId) return; this.updateE2EStatus(); - }, + }; - updateE2EStatus: async function() { + async updateE2EStatus() { const cli = MatrixClientPeg.get(); const { userId } = this.props.member; const isMe = userId === cli.getUserId(); @@ -142,7 +139,7 @@ export default createReactClass({ this.setState({ e2eStatus: anyDeviceUnverified ? "warning" : "verified", }); - }, + } getStatusMessage() { const { user } = this.props.member; @@ -150,16 +147,16 @@ export default createReactClass({ return ""; } return user._unstable_statusMessage; - }, + } - _onStatusMessageCommitted() { + _onStatusMessageCommitted = () => { // The `User` object has observed a status message change. this.setState({ statusMessage: this.getStatusMessage(), }); - }, + }; - shouldComponentUpdate: function(nextProps, nextState) { + shouldComponentUpdate(nextProps, nextState) { if ( this.member_last_modified_time === undefined || this.member_last_modified_time < nextProps.member.getLastModifiedTime() @@ -180,27 +177,27 @@ export default createReactClass({ return true; } return false; - }, + } - onClick: function(e) { + onClick = e => { dis.dispatch({ action: Action.ViewUser, member: this.props.member, }); - }, + }; - _getDisplayName: function() { + _getDisplayName() { return this.props.member.name; - }, + } - getPowerLabel: function() { + getPowerLabel() { return _t("%(userName)s (power %(powerLevelNumber)s)", { userName: this.props.member.userId, powerLevelNumber: this.props.member.powerLevel, }); - }, + } - render: function() { + render() { const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); const EntityTile = sdk.getComponent('rooms.EntityTile'); @@ -260,5 +257,5 @@ export default createReactClass({ onClick={this.onClick} /> ); - }, -}); + } +} diff --git a/src/components/views/rooms/PinnedEventTile.js b/src/components/views/rooms/PinnedEventTile.js index 924385d2262..9fad0c23914 100644 --- a/src/components/views/rooms/PinnedEventTile.js +++ b/src/components/views/rooms/PinnedEventTile.js @@ -16,7 +16,6 @@ limitations under the License. import React from "react"; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import dis from "../../../dispatcher/dispatcher"; import AccessibleButton from "../elements/AccessibleButton"; @@ -25,22 +24,23 @@ import MemberAvatar from "../avatars/MemberAvatar"; import { _t } from '../../../languageHandler'; import {formatFullDate} from '../../../DateUtils'; -export default createReactClass({ - displayName: 'PinnedEventTile', - propTypes: { +export default class PinnedEventTile extends React.Component { + static propTypes = { mxRoom: PropTypes.object.isRequired, mxEvent: PropTypes.object.isRequired, onUnpinned: PropTypes.func, - }, - onTileClicked: function() { + }; + + onTileClicked = () => { dis.dispatch({ action: 'view_room', event_id: this.props.mxEvent.getId(), highlighted: true, room_id: this.props.mxEvent.getRoomId(), }); - }, - onUnpinClicked: function() { + }; + + onUnpinClicked = () => { const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", ""); if (!pinnedEvents || !pinnedEvents.getContent().pinned) { // Nothing to do: already unpinned @@ -56,11 +56,13 @@ export default createReactClass({ }); } else if (this.props.onUnpinned) this.props.onUnpinned(); } - }, - _canUnpin: function() { + }; + + _canUnpin() { return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get()); - }, - render: function() { + } + + render() { const sender = this.props.mxEvent.getSender(); // Get the latest sender profile rather than historical const senderProfile = this.props.mxRoom.getMember(sender); @@ -100,5 +102,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js index 8630c0340e5..3ea02999767 100644 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ b/src/components/views/rooms/PinnedEventsPanel.js @@ -17,46 +17,42 @@ limitations under the License. import React from "react"; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import AccessibleButton from "../elements/AccessibleButton"; import PinnedEventTile from "./PinnedEventTile"; import { _t } from '../../../languageHandler'; import PinningUtils from "../../../utils/PinningUtils"; -export default createReactClass({ - displayName: 'PinnedEventsPanel', - propTypes: { +export default class PinnedEventsPanel extends React.Component { + static propTypes = { // The Room from the js-sdk we're going to show pinned events for room: PropTypes.object.isRequired, onCancelClick: PropTypes.func, - }, + }; - getInitialState: function() { - return { - loading: true, - }; - }, + state = { + loading: true, + }; - componentDidMount: function() { + componentDidMount() { this._updatePinnedMessages(); MatrixClientPeg.get().on("RoomState.events", this._onStateEvent); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { if (MatrixClientPeg.get()) { 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") { this._updatePinnedMessages(); } - }, + }; - _updatePinnedMessages: function() { + _updatePinnedMessages = () => { const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); if (!pinnedEvents || !pinnedEvents.getContent().pinned) { this.setState({ loading: false, pinned: [] }); @@ -85,9 +81,9 @@ export default createReactClass({ } this._updateReadState(); - }, + }; - _updateReadState: function() { + _updateReadState() { const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); if (!pinnedEvents) return; // nothing to read @@ -107,9 +103,9 @@ export default createReactClass({ event_ids: readStateEvents, }); } - }, + } - _getPinnedTiles: function() { + _getPinnedTiles() { if (this.state.pinned.length === 0) { return (
{ _t("No pinned messages.") }
); } @@ -120,9 +116,9 @@ export default createReactClass({ mxEvent={context.event} onUnpinned={this._updatePinnedMessages} />); }); - }, + } - render: function() { + render() { let tiles =
{ _t("Loading...") }
; if (this.state && !this.state.loading) { tiles = this._getPinnedTiles(); @@ -139,5 +135,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/rooms/PresenceLabel.js b/src/components/views/rooms/PresenceLabel.js index f9dcd7e89de..ff1460ca215 100644 --- a/src/components/views/rooms/PresenceLabel.js +++ b/src/components/views/rooms/PresenceLabel.js @@ -16,15 +16,12 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; -export default createReactClass({ - displayName: 'PresenceLabel', - - propTypes: { +export default class PresenceLabel extends React.Component { + static propTypes = { // number of milliseconds ago this user was last active. // zero = unknown activeAgo: PropTypes.number, @@ -35,18 +32,16 @@ export default createReactClass({ // offline, online, etc presenceState: PropTypes.string, - }, + }; - getDefaultProps: function() { - return { - ago: -1, - presenceState: null, - }; - }, + static defaultProps = { + activeAgo: -1, + presenceState: null, + }; // 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. - getDuration: function(time) { + getDuration(time) { if (!time) return; const t = parseInt(time / 1000); const s = t % 60; @@ -66,9 +61,9 @@ export default createReactClass({ return _t("%(duration)sh", {duration: h}); } return _t("%(duration)sd", {duration: d}); - }, + } - getPrettyPresence: function(presence, activeAgo, currentlyActive) { + getPrettyPresence(presence, activeAgo, currentlyActive) { if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) { const duration = this.getDuration(activeAgo); if (presence === "online") return _t("Online for %(duration)s", { duration: duration }); @@ -81,13 +76,13 @@ export default createReactClass({ if (presence === "offline") return _t("Offline"); return _t("Unknown"); } - }, + } - render: function() { + render() { return (
{ this.getPrettyPresence(this.props.presenceState, this.props.activeAgo, this.props.currentlyActive) }
); - }, -}); + } +} diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index 1c490f019ee..c19247ef5af 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -17,7 +17,6 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import '../../../VelocityBounce'; import { _t } from '../../../languageHandler'; import {formatDate} from '../../../DateUtils'; @@ -33,10 +32,8 @@ try { } catch (e) { } -export default createReactClass({ - displayName: 'ReadReceiptMarker', - - propTypes: { +export default class ReadReceiptMarker extends React.Component { + static propTypes = { // the RoomMember to show the RR for member: PropTypes.object, // userId to fallback the avatar to @@ -70,30 +67,27 @@ export default createReactClass({ // True to show twelve hour format, false otherwise showTwelveHour: PropTypes.bool, - }, + }; - getDefaultProps: function() { - return { - leftOffset: 0, - }; - }, - - getInitialState: function() { - // 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. - return { - suppressDisplay: !this.props.suppressAnimation, - }; - }, + static defaultProps = { + leftOffset: 0, + }; + + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this._avatar = createRef(); - }, - componentWillUnmount: function() { + 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, + }; + } + + componentWillUnmount() { // before we remove the rr, store its location in the map, so that if // it reappears, it can be animated from the right place. const rrInfo = this.props.readReceiptInfo; @@ -112,9 +106,9 @@ export default createReactClass({ rrInfo.top = avatarNode.offsetTop; rrInfo.left = avatarNode.offsetLeft; rrInfo.parent = avatarNode.offsetParent; - }, + } - componentDidMount: function() { + componentDidMount() { if (!this.state.suppressDisplay) { // we've already done our display - nothing more to do. return; @@ -172,10 +166,9 @@ export default createReactClass({ startStyles: startStyles, enterTransitionOpts: enterTransitionOpts, }); - }, - + } - render: function() { + render() { const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); if (this.state.suppressDisplay) { return
; @@ -222,5 +215,5 @@ export default createReactClass({ /> ); - }, -}); + } +} diff --git a/src/components/views/rooms/RoomDetailList.js b/src/components/views/rooms/RoomDetailList.js index 5b45cfc29a3..d8205aeb217 100644 --- a/src/components/views/rooms/RoomDetailList.js +++ b/src/components/views/rooms/RoomDetailList.js @@ -19,35 +19,32 @@ import dis from '../../../dispatcher/dispatcher'; import React from 'react'; import { _t } from '../../../languageHandler'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import classNames from 'classnames'; import {roomShape} from './RoomDetailRow'; -export default createReactClass({ - displayName: 'RoomDetailList', - - propTypes: { +export default class RoomDetailList extends React.Component { + static propTypes = { rooms: PropTypes.arrayOf(roomShape), className: PropTypes.string, - }, + }; - getRows: function() { + getRows() { if (!this.props.rooms) return []; const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow'); return this.props.rooms.map((room, index) => { return ; }); - }, + } - onDetailsClick: function(ev, room) { + onDetailsClick = (ev, room) => { dis.dispatch({ action: 'view_room', room_id: room.roomId, room_alias: room.canonicalAlias || (room.aliases || [])[0], }); - }, + }; render() { const rows = this.getRows(); @@ -64,5 +61,5 @@ export default createReactClass({ return
{ rooms }
; - }, -}); + } +} diff --git a/src/components/views/rooms/RoomDetailRow.js b/src/components/views/rooms/RoomDetailRow.js index a88fae9e422..667f8219221 100644 --- a/src/components/views/rooms/RoomDetailRow.js +++ b/src/components/views/rooms/RoomDetailRow.js @@ -20,7 +20,6 @@ import { _t } from '../../../languageHandler'; import { linkifyElement } from '../../../HtmlUtils'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; export function getDisplayAliasForRoom(room) { @@ -40,47 +39,48 @@ export const roomShape = PropTypes.shape({ guestCanJoin: PropTypes.bool, }); -export default createReactClass({ - propTypes: { +export default class RoomDetailRow extends React.Component { + static propTypes = { room: roomShape, // passes ev, room as args onClick: PropTypes.func, onMouseDown: PropTypes.func, - }, + }; - _linkifyTopic: function() { - if (this._topic.current) { - linkifyElement(this._topic.current); - } - }, + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this._topic = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { this._linkifyTopic(); - }, + } - componentDidUpdate: function() { + componentDidUpdate() { this._linkifyTopic(); - }, + } - onClick: function(ev) { + _linkifyTopic() { + if (this._topic.current) { + linkifyElement(this._topic.current); + } + } + + onClick = (ev) => { ev.preventDefault(); if (this.props.onClick) { this.props.onClick(ev, this.props.room); } - }, + }; - onTopicClick: function(ev) { + onTopicClick = (ev) => { // When clicking a link in the topic, prevent the event being propagated // to `onClick`. ev.stopPropagation(); - }, + }; - render: function() { + render() { const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const room = this.props.room; @@ -118,5 +118,5 @@ export default createReactClass({ { room.numJoinedMembers } ; - }, -}); + } +} diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index fe5b12c1c00..2a44f53d219 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -17,7 +17,6 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import classNames from 'classnames'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -35,10 +34,8 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import {DefaultTagID} from "../../../stores/room-list/models"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -export default createReactClass({ - displayName: 'RoomHeader', - - propTypes: { +export default class RoomHeader extends React.Component { + static propTypes = { room: PropTypes.object, oobData: PropTypes.object, inRoom: PropTypes.bool, @@ -48,22 +45,21 @@ export default createReactClass({ onLeaveClick: PropTypes.func, onCancelClick: PropTypes.func, e2eStatus: PropTypes.string, - }, - - getDefaultProps: function() { - return { - editing: false, - inRoom: false, - onCancelClick: null, - }; - }, - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { + }; + + static defaultProps = { + editing: false, + inRoom: false, + onCancelClick: null, + }; + + constructor(props) { + super(props); + this._topic = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { const cli = MatrixClientPeg.get(); cli.on("RoomState.events", this._onRoomStateEvents); cli.on("Room.accountData", this._onRoomAccountData); @@ -74,15 +70,15 @@ export default createReactClass({ if (this.props.room) { this.props.room.on("Room.name", this._onRoomNameChange); } - }, + } - componentDidUpdate: function() { + componentDidUpdate() { if (this._topic.current) { linkifyElement(this._topic.current); } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { if (this.props.room) { this.props.room.removeListener("Room.name", this._onRoomNameChange); } @@ -91,41 +87,41 @@ export default createReactClass({ cli.removeListener("RoomState.events", this._onRoomStateEvents); cli.removeListener("Room.accountData", this._onRoomAccountData); } - }, + } - _onRoomStateEvents: function(event, state) { + _onRoomStateEvents = (event, state) => { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { return; } // redisplay the room name, topic, etc. this._rateLimitedUpdate(); - }, + }; - _onRoomAccountData: function(event, room) { + _onRoomAccountData = (event, room) => { if (!this.props.room || room.roomId !== this.props.room.roomId) return; if (event.getType() !== "im.vector.room.read_pins") return; this._rateLimitedUpdate(); - }, + }; - _rateLimitedUpdate: new RateLimitedFunc(function() { + _rateLimitedUpdate = new RateLimitedFunc(function() { /* eslint-disable babel/no-invalid-this */ this.forceUpdate(); - }, 500), + }, 500); - _onRoomNameChange: function(room) { + _onRoomNameChange = (room) => { this.forceUpdate(); - }, + }; - onShareRoomClick: function(ev) { + onShareRoomClick = (ev) => { const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); Modal.createTrackedDialog('share room dialog', '', ShareDialog, { target: this.props.room, }); - }, + }; - _hasUnreadPins: function() { + _hasUnreadPins() { const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); if (!currentPinEvent) return false; 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 return true; - }, + } - _hasPins: function() { + _hasPins() { const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); if (!currentPinEvent) return false; return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0); - }, + } - render: function() { + render() { let searchStatus = null; let cancelButton = null; let settingsButton = null; @@ -301,5 +297,5 @@ export default createReactClass({
); - }, -}); + } +} diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index dc3893785d1..f42e18372a4 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -18,7 +18,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import dis from '../../../dispatcher/dispatcher'; @@ -46,10 +45,8 @@ const MessageCase = Object.freeze({ OtherError: "OtherError", }); -export default createReactClass({ - displayName: 'RoomPreviewBar', - - propTypes: { +export default class RoomPreviewBar extends React.Component { + static propTypes = { onJoinClick: PropTypes.func, onRejectClick: PropTypes.func, onRejectAndIgnoreClick: PropTypes.func, @@ -86,36 +83,32 @@ export default createReactClass({ // If given, this will be how the room is referred to (eg. // in error messages). roomAlias: PropTypes.string, - }, + }; - getDefaultProps: function() { - return { - onJoinClick: function() {}, - }; - }, + static defaultProps = { + onJoinClick() {}, + }; - getInitialState: function() { - return { - busy: false, - }; - }, + state = { + busy: false, + }; - componentDidMount: function() { + componentDidMount() { this._checkInvitedEmail(); 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) { this._checkInvitedEmail(); } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { 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 // invited, fetch the user's account emails and discovery bindings so we // can check them against the email that was invited. @@ -148,14 +141,14 @@ export default createReactClass({ } this.setState({busy: false}); } - }, + } - _onCommunityUpdate: function (roomId) { + _onCommunityUpdate = (roomId) => { if (this.props.room && this.props.room.roomId !== roomId) { return; } this.forceUpdate(); // we have nothing to update - }, + }; _getMessageCase() { const isGuest = MatrixClientPeg.get().isGuest(); @@ -207,7 +200,7 @@ export default createReactClass({ } else { return MessageCase.ViewingRoom; } - }, + } _getKickOrBanInfo() { const myMember = this._getMyMember(); @@ -221,9 +214,9 @@ export default createReactClass({ kickerMember.name : myMember.events.member.getSender(); const reason = myMember.events.member.getContent().reason; return {memberName, reason}; - }, + } - _joinRule: function() { + _joinRule() { const room = this.props.room; if (room) { const joinRules = room.currentState.getStateEvents('m.room.join_rules', ''); @@ -231,14 +224,14 @@ export default createReactClass({ return joinRules.getContent().join_rule; } } - }, + } - _communityProfile: function() { + _communityProfile() { if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId); return {displayName: null, avatarMxc: null}; - }, + } - _roomName: function(atStart = false) { + _roomName(atStart = false) { let name = this.props.room ? this.props.room.name : this.props.roomAlias; const profile = this._communityProfile(); if (profile.displayName) name = profile.displayName; @@ -249,16 +242,16 @@ export default createReactClass({ } else { return _t("this room"); } - }, + } _getMyMember() { return ( this.props.room && this.props.room.getMember(MatrixClientPeg.get().getUserId()) ); - }, + } - _getInviteMember: function() { + _getInviteMember() { const {room} = this.props; if (!room) { return; @@ -270,7 +263,7 @@ export default createReactClass({ } const inviterUserId = inviteEvent.events.member.getSender(); return room.currentState.getMember(inviterUserId); - }, + } _isDMInvite() { const myMember = this._getMyMember(); @@ -280,7 +273,7 @@ export default createReactClass({ const memberEvent = myMember.events.member; const memberContent = memberEvent.getContent(); return memberContent.membership === "invite" && memberContent.is_direct; - }, + } _makeScreenAfterLogin() { return { @@ -293,17 +286,17 @@ export default createReactClass({ inviter_name: this.props.oobData ? this.props.oobData.inviterName : null, } }; - }, + } - onLoginClick: function() { + onLoginClick = () => { dis.dispatch({ action: 'start_login', screenAfterLogin: this._makeScreenAfterLogin() }); - }, + }; - onRegisterClick: function() { + onRegisterClick = () => { dis.dispatch({ action: 'start_registration', screenAfterLogin: this._makeScreenAfterLogin() }); - }, + }; - render: function() { + render() { const brand = SdkConfig.get().brand; const Spinner = sdk.getComponent('elements.Spinner'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); @@ -597,5 +590,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.js b/src/components/views/rooms/RoomUpgradeWarningBar.js index 7d5bc89034e..531428198e7 100644 --- a/src/components/views/rooms/RoomUpgradeWarningBar.js +++ b/src/components/views/rooms/RoomUpgradeWarningBar.js @@ -16,29 +16,26 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; -export default createReactClass({ - displayName: 'RoomUpgradeWarningBar', - - propTypes: { +export default class RoomUpgradeWarningBar extends React.Component { + static propTypes = { room: PropTypes.object.isRequired, recommendation: PropTypes.object.isRequired, - }, + }; - componentDidMount: function() { + componentDidMount() { const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", ""); this.setState({upgraded: tombstone && tombstone.getContent().replacement_room}); MatrixClientPeg.get().on("RoomState.events", this._onStateEvents); - }, + } - _onStateEvents: function(event, state) { + _onStateEvents = (event, state) => { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { return; } @@ -47,14 +44,14 @@ export default createReactClass({ const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", ""); this.setState({upgraded: tombstone && tombstone.getContent().replacement_room}); - }, + }; - onUpgradeClick: function() { + onUpgradeClick = () => { const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog'); Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: this.props.room}); - }, + }; - render: function() { + render() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); let doUpgradeWarnings = ( @@ -117,5 +114,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/rooms/SearchBar.js b/src/components/views/rooms/SearchBar.js index 1d2e7ed6028..767f5a35f51 100644 --- a/src/components/views/rooms/SearchBar.js +++ b/src/components/views/rooms/SearchBar.js @@ -15,35 +15,31 @@ limitations under the License. */ import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; import AccessibleButton from "../elements/AccessibleButton"; import classNames from "classnames"; import { _t } from '../../../languageHandler'; import {Key} from "../../../Keyboard"; -export default createReactClass({ - displayName: 'SearchBar', +export default class SearchBar extends React.Component { + 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(); - }, - onThisRoomClick: function() { + this.state = { + scope: 'Room', + }; + } + + onThisRoomClick = () => { this.setState({ scope: 'Room' }, () => this._searchIfQuery()); - }, + }; - onAllRoomsClick: function() { + onAllRoomsClick = () => { this.setState({ scope: 'All' }, () => this._searchIfQuery()); - }, + }; - onSearchChange: function(e) { + onSearchChange = (e) => { switch (e.key) { case Key.ENTER: this.onSearch(); @@ -52,19 +48,19 @@ export default createReactClass({ this.props.onCancelClick(); break; } - }, + }; - _searchIfQuery: function() { + _searchIfQuery() { if (this._search_term.current.value) { this.onSearch(); } - }, + } - onSearch: function() { + onSearch = () => { this.props.onSearch(this._search_term.current.value, this.state.scope); - }, + }; - render: function() { + render() { const searchButtonClasses = classNames("mx_SearchBar_searchButton", { mx_SearchBar_searching: this.props.searchInProgress, }); @@ -92,5 +88,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/rooms/SearchResultTile.js b/src/components/views/rooms/SearchResultTile.js index 57856a3a5b9..136bd23729e 100644 --- a/src/components/views/rooms/SearchResultTile.js +++ b/src/components/views/rooms/SearchResultTile.js @@ -17,14 +17,11 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import {haveTileForEvent} from "./EventTile"; -export default createReactClass({ - displayName: 'SearchResult', - - propTypes: { +export default class SearchResultTile extends React.Component { + static propTypes = { // a matrix-js-sdk SearchResult containing the details of this result searchResult: PropTypes.object.isRequired, @@ -35,9 +32,9 @@ export default createReactClass({ resultLink: PropTypes.string, onHeightChanged: PropTypes.func, - }, + }; - render: function() { + render() { const DateSeparator = sdk.getComponent('messages.DateSeparator'); const EventTile = sdk.getComponent('rooms.EventTile'); const result = this.props.searchResult; @@ -66,5 +63,5 @@ export default createReactClass({
  • { ret }
  • ); - }, -}); + } +} diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.js index f22d9fc1d0d..1c78253eff0 100644 --- a/src/components/views/rooms/SimpleRoomHeader.js +++ b/src/components/views/rooms/SimpleRoomHeader.js @@ -16,7 +16,6 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import AccessibleButton from '../elements/AccessibleButton'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -37,18 +36,16 @@ export function CancelButton(props) { * A stripped-down room header used for things like the user settings * and room directory. */ -export default createReactClass({ - displayName: 'SimpleRoomHeader', - - propTypes: { +export default class SimpleRoomHeader extends React.Component { + static propTypes = { title: PropTypes.string, onCancelClick: PropTypes.func, // `src` to a TintableSvg. Optional. icon: PropTypes.string, - }, + }; - render: function() { + render() { let cancelButton; let icon; if (this.props.onCancelClick) { @@ -73,5 +70,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/rooms/TopUnreadMessagesBar.js b/src/components/views/rooms/TopUnreadMessagesBar.js index 23d41059c4f..9ac3c49ef45 100644 --- a/src/components/views/rooms/TopUnreadMessagesBar.js +++ b/src/components/views/rooms/TopUnreadMessagesBar.js @@ -18,19 +18,16 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; -export default createReactClass({ - displayName: 'TopUnreadMessagesBar', - - propTypes: { +export default class TopUnreadMessagesBar extends React.Component { + static propTypes = { onScrollUpClick: PropTypes.func, onCloseClick: PropTypes.func, - }, + }; - render: function() { + render() { return (
    ); - }, -}); + } +} diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.js index b56a75582f8..c796142e789 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.js @@ -17,16 +17,13 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import * as WhoIsTyping from '../../../WhoIsTyping'; import Timer from '../../../utils/Timer'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import MemberAvatar from '../avatars/MemberAvatar'; -export default createReactClass({ - displayName: 'WhoIsTypingTile', - - propTypes: { +export default class WhoIsTypingTile extends React.Component { + static propTypes = { // the room this statusbar is representing. room: PropTypes.object.isRequired, onShown: PropTypes.func, @@ -34,32 +31,28 @@ export default createReactClass({ // Number of names to display in typing indication. E.g. set to 3, will // result in "X, Y, Z and 100 others are typing." whoIsTypingLimit: PropTypes.number, - }, - - getDefaultProps: function() { - return { - whoIsTypingLimit: 3, - }; - }, - - getInitialState: function() { - return { - usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), - // a map with userid => Timer to delay - // hiding the "x is typing" message for a - // user so hiding it can coincide - // with the sent message by the other side - // resulting in less timeline jumpiness - delayedStopTypingTimers: {}, - }; - }, - - componentDidMount: function() { + }; + + static defaultProps = { + whoIsTypingLimit: 3, + }; + + state = { + usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), + // a map with userid => Timer to delay + // hiding the "x is typing" message for a + // user so hiding it can coincide + // with the sent message by the other side + // resulting in less timeline jumpiness + delayedStopTypingTimers: {}, + }; + + componentDidMount() { MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping); MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline); - }, + } - componentDidUpdate: function(_, prevState) { + componentDidUpdate(_, prevState) { const wasVisible = this._isVisible(prevState); const isVisible = this._isVisible(this.state); if (this.props.onShown && !wasVisible && isVisible) { @@ -67,9 +60,9 @@ export default createReactClass({ } else if (this.props.onHidden && wasVisible && !isVisible) { 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... const client = MatrixClientPeg.get(); if (client) { @@ -77,17 +70,17 @@ export default createReactClass({ client.removeListener("Room.timeline", this.onRoomTimeline); } 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; - }, + } - isVisible: function() { + isVisible = () => { return this._isVisible(this.state); - }, + }; - onRoomTimeline: function(event, room) { + onRoomTimeline = (event, room) => { if (room && room.roomId === this.props.room.roomId) { const userId = event.getSender(); // remove user from usersTyping @@ -96,15 +89,15 @@ export default createReactClass({ // abort timer if any this._abortUserTimer(userId); } - }, + }; - onRoomMemberTyping: function(ev, member) { + onRoomMemberTyping = (ev, member) => { const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room); this.setState({ delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping), usersTyping, }); - }, + }; _updateDelayedStopTypingTimers(usersTyping) { const usersThatStoppedTyping = this.state.usersTyping.filter((a) => { @@ -142,26 +135,26 @@ export default createReactClass({ }, delayedStopTypingTimers); return delayedStopTypingTimers; - }, + } - _abortUserTimer: function(userId) { + _abortUserTimer(userId) { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { timer.abort(); this._removeUserTimer(userId); } - }, + } - _removeUserTimer: function(userId) { + _removeUserTimer(userId) { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers); delete delayedStopTypingTimers[userId]; this.setState({delayedStopTypingTimers}); } - }, + } - _renderTypingIndicatorAvatars: function(users, limit) { + _renderTypingIndicatorAvatars(users, limit) { let othersCount = 0; if (users.length > limit) { othersCount = users.length - limit + 1; @@ -190,9 +183,9 @@ export default createReactClass({ } return avatars; - }, + } - render: function() { + render() { let usersTyping = this.state.usersTyping; const stoppedUsersOnTimer = Object.keys(this.state.delayedStopTypingTimers) .map((userId) => this.props.room.getMember(userId)); @@ -222,5 +215,5 @@ export default createReactClass({ ); - }, -}); + } +} diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.js index d8baafd1bbc..a6fc60ceb95 100644 --- a/src/components/views/settings/ChangeAvatar.js +++ b/src/components/views/settings/ChangeAvatar.js @@ -16,14 +16,12 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; -export default createReactClass({ - displayName: 'ChangeAvatar', - propTypes: { +export default class ChangeAvatar extends React.Component { + static propTypes = { initialAvatarUrl: PropTypes.string, room: PropTypes.object, // if false, you need to call changeAvatar.onFileSelected yourself. @@ -31,36 +29,36 @@ export default createReactClass({ width: PropTypes.number, height: PropTypes.number, className: PropTypes.string, - }, + }; - Phases: { + static Phases = { Display: "display", Uploading: "uploading", Error: "error", - }, - - getDefaultProps: function() { - return { - showUploadSection: true, - className: "", - width: 80, - height: 80, - }; - }, + }; + + static defaultProps = { + showUploadSection: true, + className: "", + width: 80, + height: 80, + }; + + constructor(props) { + super(props); - getInitialState: function() { - return { + this.state = { avatarUrl: this.props.initialAvatarUrl, - phase: this.Phases.Display, + phase: ChangeAvatar.Phases.Display, }; - }, + } - componentDidMount: function() { + componentDidMount() { MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents); - }, + } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps: function(newProps) { + UNSAFE_componentWillReceiveProps(newProps) { if (this.avatarSet) { // don't clobber what the user has just set return; @@ -68,15 +66,15 @@ export default createReactClass({ this.setState({ avatarUrl: newProps.initialAvatarUrl, }); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); } - }, + } - onRoomStateEvents: function(ev) { + onRoomStateEvents = (ev) => { if (!this.props.room) { return; } @@ -90,13 +88,13 @@ export default createReactClass({ this.avatarSet = false; this.setState({}); // force update } - }, + }; - setAvatarFromFile: function(file) { + setAvatarFromFile(file) { let newUrl = null; this.setState({ - phase: this.Phases.Uploading, + phase: ChangeAvatar.Phases.Uploading, }); const self = this; const httpPromise = MatrixClientPeg.get().uploadContent(file).then(function(url) { @@ -115,31 +113,31 @@ export default createReactClass({ httpPromise.then(function() { self.setState({ - phase: self.Phases.Display, + phase: ChangeAvatar.Phases.Display, avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl), }); }, function(error) { self.setState({ - phase: self.Phases.Error, + phase: ChangeAvatar.Phases.Error, }); self.onError(error); }); return httpPromise; - }, + } - onFileSelected: function(ev) { + onFileSelected = (ev) => { this.avatarSet = true; return this.setAvatarFromFile(ev.target.files[0]); - }, + }; - onError: function(error) { + onError = (error) => { this.setState({ errorText: _t("Failed to upload profile picture!"), }); - }, + }; - render: function() { + render() { let avatarImg; // Having just set an avatar we just display that since it will take a little // time to propagate through to the RoomAvatar. @@ -165,8 +163,8 @@ export default createReactClass({ } switch (this.state.phase) { - case this.Phases.Display: - case this.Phases.Error: + case ChangeAvatar.Phases.Display: + case ChangeAvatar.Phases.Error: return (
    @@ -175,11 +173,11 @@ export default createReactClass({ { uploadSection }
    ); - case this.Phases.Uploading: + case ChangeAvatar.Phases.Uploading: var Loader = sdk.getComponent("elements.Spinner"); return ( ); } - }, -}); + } +} diff --git a/src/components/views/settings/ChangeDisplayName.js b/src/components/views/settings/ChangeDisplayName.js index fdc55a4f52c..538e52d0caa 100644 --- a/src/components/views/settings/ChangeDisplayName.js +++ b/src/components/views/settings/ChangeDisplayName.js @@ -17,15 +17,12 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; -export default createReactClass({ - displayName: 'ChangeDisplayName', - - _getDisplayName: async function() { +export default class ChangeDisplayName extends React.Component { + _getDisplayName = async () => { const cli = MatrixClientPeg.get(); try { const res = await cli.getProfileInfo(cli.getUserId()); @@ -33,16 +30,16 @@ export default createReactClass({ } catch (e) { throw new Error("Failed to fetch display name"); } - }, + }; - _changeDisplayName: function(newDisplayname) { + _changeDisplayName = (newDisplayname) => { const cli = MatrixClientPeg.get(); return cli.setDisplayName(newDisplayname).catch(function(e) { throw new Error("Failed to set display name", e); }); - }, + }; - render: function() { + render() { const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer'); return ( ); - }, -}); + } +} diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js index 47fcae5c6f5..725f04dede9 100644 --- a/src/components/views/settings/ChangePassword.js +++ b/src/components/views/settings/ChangePassword.js @@ -18,7 +18,6 @@ limitations under the License. import Field from "../elements/Field"; import React from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import dis from "../../../dispatcher/dispatcher"; import AccessibleButton from '../elements/AccessibleButton'; @@ -28,10 +27,8 @@ import Modal from "../../../Modal"; import sessionStore from '../../../stores/SessionStore'; -export default createReactClass({ - displayName: 'ChangePassword', - - propTypes: { +export default class ChangePassword extends React.Component { + static propTypes = { onFinished: PropTypes.func, onError: PropTypes.func, onCheckPassword: PropTypes.func, @@ -41,65 +38,61 @@ export default createReactClass({ confirm: PropTypes.bool, // Whether to autoFocus the new password input autoFocusNewPasswordInput: PropTypes.bool, - }, + }; - Phases: { + static Phases = { Edit: "edit", Uploading: "uploading", Error: "error", - }, + }; - getDefaultProps: function() { - return { - onFinished: function() {}, - onError: function() {}, - onCheckPassword: function(oldPass, newPass, confirmPass) { - if (newPass !== confirmPass) { - return { - error: _t("New passwords don't match"), - }; - } else if (!newPass || newPass.length === 0) { - return { - error: _t("Passwords can't be empty"), - }; - } - }, - confirm: true, - }; - }, + static defaultProps = { + onFinished() {}, + onError() {}, + onCheckPassword(oldPass, newPass, confirmPass) { + if (newPass !== confirmPass) { + return { + error: _t("New passwords don't match"), + }; + } else if (!newPass || newPass.length === 0) { + return { + error: _t("Passwords can't be empty"), + }; + } + }, + confirm: true, + } - getInitialState: function() { - return { - phase: this.Phases.Edit, - cachedPassword: null, - oldPassword: "", - newPassword: "", - newPasswordConfirm: "", - }; - }, + state = { + phase: ChangePassword.Phases.Edit, + cachedPassword: null, + oldPassword: "", + newPassword: "", + newPasswordConfirm: "", + }; - componentDidMount: function() { + componentDidMount() { this._sessionStore = sessionStore; this._sessionStoreToken = this._sessionStore.addListener( this._setStateFromSessionStore, ); this._setStateFromSessionStore(); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { if (this._sessionStoreToken) { this._sessionStoreToken.remove(); } - }, + } - _setStateFromSessionStore: function() { + _setStateFromSessionStore = () => { this.setState({ cachedPassword: this._sessionStore.getCachedPassword(), }); - }, + }; - changePassword: function(oldPassword, newPassword) { + changePassword(oldPassword, newPassword) { const cli = MatrixClientPeg.get(); if (!this.props.confirm) { @@ -136,9 +129,9 @@ export default createReactClass({ } }, }); - }, + } - _changePassword: function(cli, oldPassword, newPassword) { + _changePassword(cli, oldPassword, newPassword) { const authDict = { type: 'm.login.password', identifier: { @@ -152,7 +145,7 @@ export default createReactClass({ }; this.setState({ - phase: this.Phases.Uploading, + phase: ChangePassword.Phases.Uploading, }); cli.setPassword(authDict, newPassword).then(() => { @@ -172,51 +165,51 @@ export default createReactClass({ this.props.onError(err); }).finally(() => { this.setState({ - phase: this.Phases.Edit, + phase: ChangePassword.Phases.Edit, oldPassword: "", newPassword: "", newPasswordConfirm: "", }); }); - }, + } - _optionallySetEmail: function() { + _optionallySetEmail() { // Ask for an email otherwise the user has no way to reset their password const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog"); const modal = Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, { title: _t('Do you want to set an email address?'), }); return modal.finished.then(([confirmed]) => confirmed); - }, + } - _onExportE2eKeysClicked: function() { + _onExportE2eKeysClicked = () => { Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password', import('../../../async-components/views/dialogs/ExportE2eKeysDialog'), { matrixClient: MatrixClientPeg.get(), }, ); - }, + }; - onChangeOldPassword(ev) { + onChangeOldPassword = (ev) => { this.setState({ oldPassword: ev.target.value, }); - }, + }; - onChangeNewPassword(ev) { + onChangeNewPassword = (ev) => { this.setState({ newPassword: ev.target.value, }); - }, + }; - onChangeNewPasswordConfirm(ev) { + onChangeNewPasswordConfirm = (ev) => { this.setState({ newPasswordConfirm: ev.target.value, }); - }, + }; - onClickChange: function(ev) { + onClickChange = (ev) => { ev.preventDefault(); const oldPassword = this.state.cachedPassword || this.state.oldPassword; const newPassword = this.state.newPassword; @@ -229,9 +222,9 @@ export default createReactClass({ } else { this.changePassword(oldPassword, newPassword); } - }, + }; - render: function() { + render() { // TODO: Live validation on `new pw == confirm pw` const rowClassName = this.props.rowClassName; @@ -252,7 +245,7 @@ export default createReactClass({ } switch (this.state.phase) { - case this.Phases.Edit: + case ChangePassword.Phases.Edit: const passwordLabel = this.state.cachedPassword ? _t('Password') : _t('New Password'); return ( @@ -282,7 +275,7 @@ export default createReactClass({ ); - case this.Phases.Uploading: + case ChangePassword.Phases.Uploading: var Loader = sdk.getComponent("elements.Spinner"); return (
    @@ -290,5 +283,5 @@ export default createReactClass({
    ); } - }, -}); + } +} diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js index 5c16e1ed4f5..6eaf55279bc 100644 --- a/src/components/views/settings/Notifications.js +++ b/src/components/views/settings/Notifications.js @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import createReactClass from 'create-react-class'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; @@ -65,46 +64,42 @@ function portLegacyActions(actions) { } } -export default createReactClass({ - displayName: 'Notifications', - - phases: { +export default class Notifications extends React.Component { + static phases = { LOADING: "LOADING", // The component is loading or sending data to the hs DISPLAY: "DISPLAY", // The component is ready and display data ERROR: "ERROR", // There was an error - }, - - getInitialState: function() { - return { - phase: this.phases.LOADING, - masterPushRule: undefined, // The master rule ('.m.rule.master') - vectorPushRules: [], // HS default push rules displayed in Vector UI - vectorContentRules: { // Keyword push rules displayed in Vector UI - vectorState: PushRuleVectorState.ON, - rules: [], - }, - externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI - externalContentRules: [], // Keyword push rules that have been defined outside Vector UI - threepids: [], // used for email notifications - }; - }, - - componentDidMount: function() { + }; + + state = { + phase: Notifications.phases.LOADING, + masterPushRule: undefined, // The master rule ('.m.rule.master') + vectorPushRules: [], // HS default push rules displayed in Vector UI + vectorContentRules: { // Keyword push rules displayed in Vector UI + vectorState: PushRuleVectorState.ON, + rules: [], + }, + externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI + externalContentRules: [], // Keyword push rules that have been defined outside Vector UI + threepids: [], // used for email notifications + }; + + componentDidMount() { this._refreshFromServer(); - }, + } - onEnableNotificationsChange: function(checked) { + onEnableNotificationsChange = (checked) => { const self = this; this.setState({ - phase: this.phases.LOADING, + phase: Notifications.phases.LOADING, }); MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).then(function() { self._refreshFromServer(); }); - }, + }; - onEnableDesktopNotificationsChange: function(checked) { + onEnableDesktopNotificationsChange = (checked) => { SettingsStore.setValue( "notificationsEnabled", null, SettingLevel.DEVICE, @@ -112,9 +107,9 @@ export default createReactClass({ ).finally(() => { this.forceUpdate(); }); - }, + }; - onEnableDesktopNotificationBodyChange: function(checked) { + onEnableDesktopNotificationBodyChange = (checked) => { SettingsStore.setValue( "notificationBodyEnabled", null, SettingLevel.DEVICE, @@ -122,9 +117,9 @@ export default createReactClass({ ).finally(() => { this.forceUpdate(); }); - }, + }; - onEnableAudioNotificationsChange: function(checked) { + onEnableAudioNotificationsChange = (checked) => { SettingsStore.setValue( "audioNotificationsEnabled", null, SettingLevel.DEVICE, @@ -132,7 +127,7 @@ export default createReactClass({ ).finally(() => { this.forceUpdate(); }); - }, + }; /* * Returns the email pusher (pusher of type 'email') for a given @@ -140,7 +135,7 @@ export default createReactClass({ * pushers are unique over (app ID, pushkey), there will be at most * one such pusher. */ - getEmailPusher: function(pushers, address) { + getEmailPusher(pushers, address) { if (pushers === undefined) { return undefined; } @@ -150,9 +145,9 @@ export default createReactClass({ } } return undefined; - }, + } - onEnableEmailNotificationsChange: function(address, checked) { + onEnableEmailNotificationsChange = (address, checked) => { let emailPusherPromise; if (checked) { const data = {}; @@ -181,9 +176,9 @@ export default createReactClass({ description: _t('An error occurred whilst saving your email notification preferences.'), }); }); - }, + }; - onNotifStateButtonClicked: function(event) { + onNotifStateButtonClicked = (event) => { // FIXME: use .bind() rather than className metadata here surely const vectorRuleId = event.target.className.split("-")[0]; const newPushRuleVectorState = event.target.className.split("-")[1]; @@ -196,11 +191,9 @@ export default createReactClass({ this._setPushRuleVectorState(rule, newPushRuleVectorState); } } - }, - - onKeywordsClicked: function(event) { - const self = this; + }; + onKeywordsClicked = (event) => { // Compute the keywords list to display let keywords = []; for (const i in this.state.vectorContentRules.rules) { @@ -223,7 +216,7 @@ export default createReactClass({ description: _t('Enter keywords separated by a comma:'), button: _t('OK'), value: keywords, - onFinished: function onFinished(should_leave, newValue) { + onFinished: (should_leave, newValue) => { if (should_leave && newValue !== keywords) { let newKeywords = newValue.split(','); for (const i in newKeywords) { @@ -238,25 +231,25 @@ export default createReactClass({ return array; }, []); - self._setKeywords(newKeywords); + this._setKeywords(newKeywords); } }, }); - }, + }; - getRule: function(vectorRuleId) { + getRule(vectorRuleId) { for (const i in this.state.vectorPushRules) { const rule = this.state.vectorPushRules[i]; if (rule.vectorRuleId === vectorRuleId) { return rule; } } - }, + } - _setPushRuleVectorState: function(rule, newPushRuleVectorState) { + _setPushRuleVectorState(rule, newPushRuleVectorState) { if (rule && rule.vectorState !== newPushRuleVectorState) { this.setState({ - phase: this.phases.LOADING, + phase: Notifications.phases.LOADING, }); const self = this; @@ -288,9 +281,9 @@ export default createReactClass({ }); }); } - }, + } - _setKeywordsPushRuleVectorState: function(newPushRuleVectorState) { + _setKeywordsPushRuleVectorState(newPushRuleVectorState) { // Is there really a change? if (this.state.vectorContentRules.vectorState === newPushRuleVectorState || this.state.vectorContentRules.rules.length === 0) { @@ -301,7 +294,7 @@ export default createReactClass({ const cli = MatrixClientPeg.get(); this.setState({ - phase: this.phases.LOADING, + phase: Notifications.phases.LOADING, }); // Update all rules in self.state.vectorContentRules @@ -356,11 +349,11 @@ export default createReactClass({ onFinished: self._refreshFromServer, }); }); - }, + } - _setKeywords: function(newKeywords) { + _setKeywords(newKeywords) { this.setState({ - phase: this.phases.LOADING, + phase: Notifications.phases.LOADING, }); const self = this; @@ -440,19 +433,19 @@ export default createReactClass({ self._refreshFromServer(); }, onError); }, onError); - }, + } // Create a push rule but disabled - _addDisabledPushRule: function(scope, kind, ruleId, body) { + _addDisabledPushRule(scope, kind, ruleId, body) { const cli = MatrixClientPeg.get(); return cli.addPushRule(scope, kind, ruleId, body).then(() => cli.setPushRuleEnabled(scope, kind, ruleId, false), ); - }, + } // Check if any legacy im.vector rules need to be ported to the new API // for overriding the actions of default rules. - _portRulesToNewAPI: function(rulesets) { + _portRulesToNewAPI(rulesets) { const needsUpdate = []; const cli = MatrixClientPeg.get(); @@ -485,9 +478,9 @@ export default createReactClass({ // Otherwise return the rules that we already have. return rulesets; } - }, + } - _refreshFromServer: function() { + _refreshFromServer = () => { const self = this; const pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) { /// XXX seriously? wtf is this? @@ -636,12 +629,12 @@ export default createReactClass({ Promise.all([pushRulesPromise, pushersPromise]).then(function() { self.setState({ - phase: self.phases.DISPLAY, + phase: Notifications.phases.DISPLAY, }); }, function(error) { console.error(error); self.setState({ - phase: self.phases.ERROR, + phase: Notifications.phases.ERROR, }); }).finally(() => { // actually explicitly update our state having been deep-manipulating it @@ -655,9 +648,9 @@ export default createReactClass({ }); MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids})); - }, + }; - _onClearNotifications: function() { + _onClearNotifications = () => { const cli = MatrixClientPeg.get(); cli.getRooms().forEach(r => { @@ -666,9 +659,9 @@ export default createReactClass({ if (events.length) cli.sendReadReceipt(events.pop()); } }); - }, + }; - _updatePushRuleActions: function(rule, actions, enabled) { + _updatePushRuleActions(rule, actions, enabled) { const cli = MatrixClientPeg.get(); return cli.setPushRuleActions( @@ -681,9 +674,9 @@ export default createReactClass({ ); } }); - }, + } - renderNotifRulesTableRow: function(title, className, pushRuleVectorState) { + renderNotifRulesTableRow(title, className, pushRuleVectorState) { return ( @@ -712,9 +705,9 @@ export default createReactClass({ ); - }, + } - renderNotifRulesTableRows: function() { + renderNotifRulesTableRows() { const rows = []; for (const i in this.state.vectorPushRules) { const rule = this.state.vectorPushRules[i]; @@ -726,9 +719,9 @@ export default createReactClass({ rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.vectorState)); } return rows; - }, + } - hasEmailPusher: function(pushers, address) { + hasEmailPusher(pushers, address) { if (pushers === undefined) { return false; } @@ -738,17 +731,17 @@ export default createReactClass({ } } return false; - }, + } - emailNotificationsRow: function(address, label) { + emailNotificationsRow(address, label) { return ; - }, + } - render: function() { + render() { let spinner; - if (this.state.phase === this.phases.LOADING) { + if (this.state.phase === Notifications.phases.LOADING) { const Loader = sdk.getComponent("elements.Spinner"); spinner = ; } @@ -910,5 +903,5 @@ export default createReactClass({
    ); - }, -}); + } +} diff --git a/src/components/views/voip/VideoFeed.js b/src/components/views/voip/VideoFeed.js index 527b0719426..a0330f8cb18 100644 --- a/src/components/views/voip/VideoFeed.js +++ b/src/components/views/voip/VideoFeed.js @@ -17,44 +17,42 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; -export default createReactClass({ - displayName: 'VideoFeed', - - propTypes: { +export default class VideoFeed extends React.Component { + static propTypes = { // maxHeight style attribute for the video element maxHeight: PropTypes.number, // a callback which is called when the video element is resized // due to a change in video metadata onResize: PropTypes.func, - }, + }; + + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount() { this._vid = createRef(); - }, + } componentDidMount() { this._vid.current.addEventListener('resize', this.onResize); - }, + } componentWillUnmount() { this._vid.current.removeEventListener('resize', this.onResize); - }, + } - onResize: function(e) { + onResize = (e) => { if (this.props.onResize) { this.props.onResize(e); } - }, + }; - render: function() { + render() { return ( ); - }, -}); + } +} diff --git a/src/components/views/voip/VideoView.js b/src/components/views/voip/VideoView.js index a51ab70da92..374a12e82d4 100644 --- a/src/components/views/voip/VideoView.js +++ b/src/components/views/voip/VideoView.js @@ -18,7 +18,6 @@ limitations under the License. import React, {createRef} from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import createReactClass from 'create-react-class'; import classNames from 'classnames'; import * as sdk from '../../../index'; @@ -35,10 +34,8 @@ function getFullScreenElement() { ); } -export default createReactClass({ - displayName: 'VideoView', - - propTypes: { +export default class VideoView extends React.Component { + static propTypes = { // maxHeight style attribute for the video element maxHeight: PropTypes.number, @@ -48,27 +45,28 @@ export default createReactClass({ // a callback which is called when the video element is resized due to // a change in video metadata onResize: PropTypes.func, - }, + }; + + constructor(props) { + super(props); - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { this._local = createRef(); this._remote = createRef(); - }, + } - componentDidMount: function() { + componentDidMount() { this.dispatcherRef = dis.register(this.onAction); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { dis.unregister(this.dispatcherRef); - }, + } - getRemoteVideoElement: function() { + getRemoteVideoElement = () => { return ReactDOM.findDOMNode(this._remote.current); - }, + }; - getRemoteAudioElement: function() { + getRemoteAudioElement = () => { // this needs to be somewhere at the top of the DOM which // always exists to avoid audio interruptions. // Might as well just use DOM. @@ -78,17 +76,17 @@ export default createReactClass({ + "You need to add an