From 379aeb667ce4a80941808091634186d7d1bac67c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Apr 2020 14:13:27 +0100 Subject: [PATCH 1/6] Convert LoggedInView to a PureComponent class Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LoggedInView.js | 120 +++++++++++----------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index d9980aeca6..36beec3f6c 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -1,7 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd -Copyright 2017, 2018 New Vector Ltd +Copyright 2017, 2018, 2020 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,8 +17,7 @@ limitations under the License. */ import { MatrixClient } from 'matrix-js-sdk'; -import React, {createRef} from 'react'; -import createReactClass from 'create-react-class'; +import React, {createRef, PureComponent} from 'react'; import PropTypes from 'prop-types'; import { DragDropContext } from 'react-beautiful-dnd'; @@ -61,10 +60,10 @@ function canElementReceiveInput(el) { * * Components mounted below us can access the matrix client via the react context. */ -const LoggedInView = createReactClass({ - displayName: 'LoggedInView', +class LoggedInView extends PureComponent { + static displayName = 'LoggedInView'; - propTypes: { + static propTypes = { matrixClient: PropTypes.instanceOf(MatrixClient).isRequired, page_type: PropTypes.string.isRequired, onRoomCreated: PropTypes.func, @@ -77,25 +76,18 @@ const LoggedInView = createReactClass({ viaServers: PropTypes.arrayOf(PropTypes.string), // and lots and lots of other stuff. - }, + }; - getInitialState: function() { - return { + constructor(props, context) { + super(props, context); + + this.state = { // use compact timeline view useCompactLayout: SettingsStore.getValue('useCompactLayout'), // any currently active server notice events serverNoticeEvents: [], }; - }, - componentDidMount: function() { - this.resizer = this._createResizer(); - this.resizer.attach(); - this._loadResizerPreferences(); - }, - - // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs - UNSAFE_componentWillMount: function() { // stash the MatrixClient in case we log out before we are unmounted this._matrixClient = this.props.matrixClient; @@ -118,7 +110,13 @@ const LoggedInView = createReactClass({ fixupColorFonts(); this._roomView = createRef(); - }, + } + + componentDidMount() { + this.resizer = this._createResizer(); + this.resizer.attach(); + this._loadResizerPreferences(); + } componentDidUpdate(prevProps) { // attempt to guess when a banner was opened or closed @@ -130,9 +128,9 @@ const LoggedInView = createReactClass({ ) { this.props.resizeNotifier.notifyBannersChanged(); } - }, + } - componentWillUnmount: function() { + componentWillUnmount() { document.removeEventListener('keydown', this._onNativeKeyDown, false); this._matrixClient.removeListener("accountData", this.onAccountData); this._matrixClient.removeListener("sync", this.onSync); @@ -141,7 +139,7 @@ const LoggedInView = createReactClass({ this._sessionStoreToken.remove(); } this.resizer.detach(); - }, + } // Child components assume that the client peg will not be null, so give them some // sort of assurance here by only allowing a re-render if the client is truthy. @@ -149,22 +147,22 @@ const LoggedInView = createReactClass({ // This is required because `LoggedInView` maintains its own state and if this state // updates after the client peg has been made null (during logout), then it will // attempt to re-render and the children will throw errors. - shouldComponentUpdate: function() { + shouldComponentUpdate() { return Boolean(MatrixClientPeg.get()); - }, + } - canResetTimelineInRoom: function(roomId) { + canResetTimelineInRoom = (roomId) => { if (!this._roomView.current) { return true; } return this._roomView.current.canResetTimeline(); - }, + }; - _setStateFromSessionStore() { + _setStateFromSessionStore = () => { this.setState({ userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()), }); - }, + }; _createResizer() { const classNames = { @@ -193,7 +191,7 @@ const LoggedInView = createReactClass({ collapseConfig); resizer.setClassNames(classNames); return resizer; - }, + } _loadResizerPreferences() { let lhsSize = window.localStorage.getItem("mx_lhs_size"); @@ -203,9 +201,9 @@ const LoggedInView = createReactClass({ lhsSize = 350; } this.resizer.forHandleAt(0).resize(lhsSize); - }, + } - onAccountData: function(event) { + onAccountData = (event) => { if (event.getType() === "im.vector.web.settings") { this.setState({ useCompactLayout: event.getContent().useCompactLayout, @@ -214,9 +212,9 @@ const LoggedInView = createReactClass({ if (event.getType() === "m.ignored_user_list") { dis.dispatch({action: "ignore_state_changed"}); } - }, + }; - onSync: function(syncState, oldSyncState, data) { + onSync = (syncState, oldSyncState, data) => { const oldErrCode = ( this.state.syncErrorData && this.state.syncErrorData.error && @@ -238,16 +236,16 @@ const LoggedInView = createReactClass({ if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') { this._updateServerNoticeEvents(); } - }, + }; - onRoomStateEvents: function(ev, state) { + onRoomStateEvents = (ev, state) => { const roomLists = RoomListStore.getRoomLists(); if (roomLists['m.server_notice'] && roomLists['m.server_notice'].some(r => r.roomId === ev.getRoomId())) { this._updateServerNoticeEvents(); } - }, + }; - _updateServerNoticeEvents: async function() { + _updateServerNoticeEvents = async () => { const roomLists = RoomListStore.getRoomLists(); if (!roomLists['m.server_notice']) return []; @@ -267,9 +265,9 @@ const LoggedInView = createReactClass({ this.setState({ serverNoticeEvents: pinnedEvents, }); - }, + }; - _onPaste: function(ev) { + _onPaste = (ev) => { let canReceiveInput = false; let element = ev.target; // test for all parents because the target can be a child of a contenteditable element @@ -283,7 +281,7 @@ const LoggedInView = createReactClass({ // so dispatch synchronously before paste happens dis.dispatch({action: 'focus_composer'}, true); } - }, + }; /* SOME HACKERY BELOW: @@ -307,22 +305,22 @@ const LoggedInView = createReactClass({ We also listen with a native listener on the document to get keydown events when no element is focused. Bubbling is irrelevant here as the target is the body element. */ - _onReactKeyDown: function(ev) { + _onReactKeyDown = (ev) => { // events caught while bubbling up on the root element // of this component, so something must be focused. this._onKeyDown(ev); - }, + }; - _onNativeKeyDown: function(ev) { + _onNativeKeyDown = (ev) => { // only pass this if there is no focused element. // if there is, _onKeyDown will be called by the // react keydown handler that respects the react bubbling order. if (ev.target === document.body) { this._onKeyDown(ev); } - }, + }; - _onKeyDown: function(ev) { + _onKeyDown = (ev) => { /* // Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers // Will need to find a better meta key if anyone actually cares about using this. @@ -432,19 +430,19 @@ const LoggedInView = createReactClass({ // that would prevent typing in the now-focussed composer } } - }, + }; /** * dispatch a page-up/page-down/etc to the appropriate component * @param {Object} ev The key event */ - _onScrollKeyPressed: function(ev) { + _onScrollKeyPressed = (ev) => { if (this._roomView.current) { this._roomView.current.handleScrollKey(ev); } - }, + }; - _onDragEnd: function(result) { + _onDragEnd = (result) => { // Dragged to an invalid destination, not onto a droppable if (!result.destination) { return; @@ -467,9 +465,9 @@ const LoggedInView = createReactClass({ } else if (dest.startsWith('room-sub-list-droppable_')) { this._onRoomTileEndDrag(result); } - }, + }; - _onRoomTileEndDrag: function(result) { + _onRoomTileEndDrag = (result) => { let newTag = result.destination.droppableId.split('_')[1]; let prevTag = result.source.droppableId.split('_')[1]; if (newTag === 'undefined') newTag = undefined; @@ -486,9 +484,9 @@ const LoggedInView = createReactClass({ prevTag, newTag, oldIndex, newIndex, ), true); - }, + }; - _onMouseDown: function(ev) { + _onMouseDown = (ev) => { // When the panels are disabled, clicking on them results in a mouse event // which bubbles to certain elements in the tree. When this happens, close // any settings page that is currently open (user/room/group). @@ -507,9 +505,9 @@ const LoggedInView = createReactClass({ }); } } - }, + }; - _onMouseUp: function(ev) { + _onMouseUp = (ev) => { if (!this.state.mouseDown) return; const deltaX = ev.pageX - this.state.mouseDown.x; @@ -528,13 +526,13 @@ const LoggedInView = createReactClass({ // Always clear the mouseDown state to ensure we don't accidentally // use stale values due to the mouseDown checks. this.setState({mouseDown: null}); - }, + }; - _setResizeContainerRef(div) { + _setResizeContainerRef = (div) => { this.resizeContainer = div; - }, + }; - render: function() { + render() { const LeftPanel = sdk.getComponent('structures.LeftPanel'); const RoomView = sdk.getComponent('structures.RoomView'); const UserView = sdk.getComponent('structures.UserView'); @@ -660,7 +658,7 @@ const LoggedInView = createReactClass({ ); - }, -}); + } +} export default LoggedInView; From 368e3f9e85bf8d950ac8541ea0693859c57300b1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Apr 2020 14:29:00 +0100 Subject: [PATCH 2/6] Convert LoggedInView to TypeScript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../{LoggedInView.js => LoggedInView.tsx} | 89 +++++++++++++++---- 1 file changed, 71 insertions(+), 18 deletions(-) rename src/components/structures/{LoggedInView.js => LoggedInView.tsx} (91%) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.tsx similarity index 91% rename from src/components/structures/LoggedInView.js rename to src/components/structures/LoggedInView.tsx index 36beec3f6c..e09a5c9ac6 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.tsx @@ -16,9 +16,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixClient } from 'matrix-js-sdk'; -import React, {createRef, PureComponent} from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import { MatrixClient } from 'matrix-js-sdk/src/client'; +import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { DragDropContext } from 'react-beautiful-dnd'; import {Key, isOnlyCtrlOrCmdKeyEvent, isOnlyCtrlOrCmdIgnoreShiftKeyEvent} from '../../Keyboard'; @@ -28,7 +29,7 @@ import { fixupColorFonts } from '../../utils/FontManager'; import * as sdk from '../../index'; import dis from '../../dispatcher'; import sessionStore from '../../stores/SessionStore'; -import {MatrixClientPeg} from '../../MatrixClientPeg'; +import {MatrixClientPeg, MatrixClientCreds} from '../../MatrixClientPeg'; import SettingsStore from "../../settings/SettingsStore"; import RoomListStore from "../../stores/RoomListStore"; @@ -39,6 +40,7 @@ import {Resizer, CollapseDistributor} from '../../resizer'; import MatrixClientContext from "../../contexts/MatrixClientContext"; import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts"; import HomePage from "./HomePage"; +import ResizeNotifier from "../../utils/ResizeNotifier"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. // NB. this is just for server notices rather than pinned messages in general. @@ -51,6 +53,52 @@ function canElementReceiveInput(el) { !!el.getAttribute("contenteditable"); } +interface IProps { + matrixClient: MatrixClient; + onRegistered: (credentials: MatrixClientCreds) => Promise; + viaServers?: string[]; + hideToSRUsers: boolean; + resizeNotifier: ResizeNotifier; + middleDisabled: boolean; + initialEventPixelOffset: number; + leftDisabled: boolean; + rightDisabled: boolean; + showCookieBar: boolean; + hasNewVersion: boolean; + userHasGeneratedPassword: boolean; + showNotifierToolbar: boolean; + page_type: string; + autoJoin: boolean; + thirdPartyInvite?: object; + roomOobData?: object; + currentRoomId: string; + ConferenceHandler?: object; + collapseLhs: boolean; + checkingForUpdate: boolean; + config: { + piwik: { + policyUrl: string; + }, + [key: string]: any, + }; + currentUserId?: string; + currentGroupId?: string; + currentGroupIsNew?: boolean; + version?: string; + newVersion?: string; + newVersionReleaseNotes?: string; +} +interface IState { + mouseDown?: { + x: number; + y: number; + }; + syncErrorData: any; + useCompactLayout: boolean; + serverNoticeEvents: MatrixEvent[]; + userHasGeneratedPassword: boolean; +} + /** * This is what our MatrixChat shows when we are logged in. The precise view is * determined by the page_type property. @@ -60,7 +108,7 @@ function canElementReceiveInput(el) { * * Components mounted below us can access the matrix client via the react context. */ -class LoggedInView extends PureComponent { +class LoggedInView extends React.PureComponent { static displayName = 'LoggedInView'; static propTypes = { @@ -78,10 +126,20 @@ class LoggedInView extends PureComponent { // and lots and lots of other stuff. }; + private readonly _matrixClient: MatrixClient; + private readonly _roomView: React.RefObject; + private readonly _resizeContainer: React.RefObject; + private readonly _sessionStore: sessionStore; + private readonly _sessionStoreToken: { remove: () => void }; + private resizer: Resizer; + constructor(props, context) { super(props, context); this.state = { + mouseDown: undefined, + syncErrorData: undefined, + userHasGeneratedPassword: false, // use compact timeline view useCompactLayout: SettingsStore.getValue('useCompactLayout'), // any currently active server notice events @@ -109,7 +167,8 @@ class LoggedInView extends PureComponent { fixupColorFonts(); - this._roomView = createRef(); + this._roomView = React.createRef(); + this._resizeContainer = React.createRef(); } componentDidMount() { @@ -118,12 +177,12 @@ class LoggedInView extends PureComponent { this._loadResizerPreferences(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps, prevState) { // attempt to guess when a banner was opened or closed if ( (prevProps.showCookieBar !== this.props.showCookieBar) || (prevProps.hasNewVersion !== this.props.hasNewVersion) || - (prevProps.userHasGeneratedPassword !== this.props.userHasGeneratedPassword) || + (prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword) || (prevProps.showNotifierToolbar !== this.props.showNotifierToolbar) ) { this.props.resizeNotifier.notifyBannersChanged(); @@ -186,7 +245,7 @@ class LoggedInView extends PureComponent { }, }; const resizer = new Resizer( - this.resizeContainer, + this._resizeContainer.current, CollapseDistributor, collapseConfig); resizer.setClassNames(classNames); @@ -194,10 +253,8 @@ class LoggedInView extends PureComponent { } _loadResizerPreferences() { - let lhsSize = window.localStorage.getItem("mx_lhs_size"); - if (lhsSize !== null) { - lhsSize = parseInt(lhsSize, 10); - } else { + let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size"), 10); + if (isNaN(lhsSize)) { lhsSize = 350; } this.resizer.forHandleAt(0).resize(lhsSize); @@ -528,10 +585,6 @@ class LoggedInView extends PureComponent { this.setState({mouseDown: null}); }; - _setResizeContainerRef = (div) => { - this.resizeContainer = div; - }; - render() { const LeftPanel = sdk.getComponent('structures.LeftPanel'); const RoomView = sdk.getComponent('structures.RoomView'); @@ -645,7 +698,7 @@ class LoggedInView extends PureComponent { { topBar } -
+
Date: Mon, 13 Apr 2020 14:30:52 +0100 Subject: [PATCH 3/6] prefer protected over private so that the class can be extended Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LoggedInView.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index e09a5c9ac6..8497b9bc80 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -126,12 +126,12 @@ class LoggedInView extends React.PureComponent { // and lots and lots of other stuff. }; - private readonly _matrixClient: MatrixClient; - private readonly _roomView: React.RefObject; - private readonly _resizeContainer: React.RefObject; - private readonly _sessionStore: sessionStore; - private readonly _sessionStoreToken: { remove: () => void }; - private resizer: Resizer; + protected readonly _matrixClient: MatrixClient; + protected readonly _roomView: React.RefObject; + protected readonly _resizeContainer: React.RefObject; + protected readonly _sessionStore: sessionStore; + protected readonly _sessionStoreToken: { remove: () => void }; + protected resizer: Resizer; constructor(props, context) { super(props, context); From 46f7fadf3f23e3f4eaaf3a97124ec2fe8cc4ccd3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 13 Apr 2020 14:33:45 +0100 Subject: [PATCH 4/6] delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LoggedInView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 8497b9bc80..0e2a74faae 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -315,8 +315,8 @@ class LoggedInView extends React.PureComponent { const pinnedEventIds = pinStateEvent.getContent().pinned.slice(0, MAX_PINNED_NOTICES_PER_ROOM); for (const eventId of pinnedEventIds) { const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0); - const ev = timeline.getEvents().find(ev => ev.getId() === eventId); - if (ev) pinnedEvents.push(ev); + const event = timeline.getEvents().find(ev => ev.getId() === eventId); + if (event) pinnedEvents.push(event); } } this.setState({ From 50d6551c86d1a8f49830801d2988c964fa094d1f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 14 Apr 2020 14:50:09 +0100 Subject: [PATCH 5/6] @replaceableComponent Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LoggedInView.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 0e2a74faae..ecf6a92c89 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -41,6 +41,7 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts"; import HomePage from "./HomePage"; import ResizeNotifier from "../../utils/ResizeNotifier"; +import { replaceableComponent } from '../../utils/replaceableComponent'; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. // NB. this is just for server notices rather than pinned messages in general. @@ -108,6 +109,7 @@ interface IState { * * Components mounted below us can access the matrix client via the react context. */ +@replaceableComponent("structures.LoggedInView") class LoggedInView extends React.PureComponent { static displayName = 'LoggedInView'; From 26569dea065ddb42877d8bece5db7162f7f45cf8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 14 Apr 2020 14:59:20 +0100 Subject: [PATCH 6/6] Revert "@replaceableComponent" This reverts commit 50d6551c Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LoggedInView.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index ecf6a92c89..0e2a74faae 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -41,7 +41,6 @@ import MatrixClientContext from "../../contexts/MatrixClientContext"; import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts"; import HomePage from "./HomePage"; import ResizeNotifier from "../../utils/ResizeNotifier"; -import { replaceableComponent } from '../../utils/replaceableComponent'; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. // NB. this is just for server notices rather than pinned messages in general. @@ -109,7 +108,6 @@ interface IState { * * Components mounted below us can access the matrix client via the react context. */ -@replaceableComponent("structures.LoggedInView") class LoggedInView extends React.PureComponent { static displayName = 'LoggedInView';