From 2e9cacdeb1d57537dc08dee931d8e4c87d837c59 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 10:22:43 +0200 Subject: [PATCH 01/23] Migrate JumpToBottomButton to TypeScript --- .../{JumpToBottomButton.js => JumpToBottomButton.tsx} | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) rename src/components/views/rooms/{JumpToBottomButton.js => JumpToBottomButton.tsx} (83%) diff --git a/src/components/views/rooms/JumpToBottomButton.js b/src/components/views/rooms/JumpToBottomButton.tsx similarity index 83% rename from src/components/views/rooms/JumpToBottomButton.js rename to src/components/views/rooms/JumpToBottomButton.tsx index d2e2a391a6..0b680d093d 100644 --- a/src/components/views/rooms/JumpToBottomButton.js +++ b/src/components/views/rooms/JumpToBottomButton.tsx @@ -14,11 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ +import React from "react"; import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; import classNames from 'classnames'; -export default (props) => { +interface IProps { + numUnreadMessages: number; + highlight: boolean; + onScrollToBottomClick: (e: React.MouseEvent) => void; +} + +const JumpToBottomButton: React.FC<IProps> = (props) => { const className = classNames({ 'mx_JumpToBottomButton': true, 'mx_JumpToBottomButton_highlight': props.highlight, @@ -36,3 +43,5 @@ export default (props) => { { badge } </div>); }; + +export default JumpToBottomButton; From e9e6269da7487c7b3456b149d757e08760db6136 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 10:31:18 +0200 Subject: [PATCH 02/23] Migrat ReadReceiptMarker to TypeScript --- src/components/views/rooms/EventTile.tsx | 1 + ...ReceiptMarker.js => ReadReceiptMarker.tsx} | 114 ++++++++++-------- 2 files changed, 64 insertions(+), 51 deletions(-) rename src/components/views/rooms/{ReadReceiptMarker.js => ReadReceiptMarker.tsx} (74%) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 301e33ec42..c5fbb99ede 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -240,6 +240,7 @@ interface IProps { // opaque readreceipt info for each userId; used by ReadReceiptMarker // to manage its animations. Should be an empty object when the room // first loads + // TODO: Proper typing for RR info readReceiptMap?: any; // A function which is used to check if the parent panel is being diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.tsx similarity index 74% rename from src/components/views/rooms/ReadReceiptMarker.js rename to src/components/views/rooms/ReadReceiptMarker.tsx index c9688b4d29..11e7563f11 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -16,7 +16,8 @@ limitations under the License. */ import React, { createRef } from 'react'; -import PropTypes from 'prop-types'; +import { RoomMember } from 'matrix-js-sdk/src'; + import { _t } from '../../../languageHandler'; import { formatDate } from '../../../DateUtils'; import NodeAnimator from "../../../NodeAnimator"; @@ -24,53 +25,64 @@ import * as sdk from "../../../index"; import { toPx } from "../../../utils/units"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + // the RoomMember to show the RR for + member?: RoomMember; + // userId to fallback the avatar to + // if the member hasn't been loaded yet + fallbackUserId: string; + + // number of pixels to offset the avatar from the right of its parent; + // typically a negative value. + leftOffset?: number; + + // true to hide the avatar (it will still be animated) + hidden?: boolean; + + // don't animate this RR into position + suppressAnimation?: boolean; + + // an opaque object for storing information about this user's RR in + // this room + // TODO: proper typing for RR info + readReceiptInfo: any; + + // A function which is used to check if the parent panel is being + // unmounted, to avoid unnecessary work. Should return true if we + // are being unmounted. + checkUnmounting?: () => boolean; + + // callback for clicks on this RR + onClick?: (e: React.MouseEvent) => void; + + // Timestamp when the receipt was read + timestamp?: number; + + // True to show twelve hour format, false otherwise + showTwelveHour?: boolean; +} + +interface IState { + suppressDisplay: boolean; + startStyles?: IReadReceiptMarkerStyle[]; +} + +interface IReadReceiptMarkerStyle { + top: number; + left: number; +} + @replaceableComponent("views.rooms.ReadReceiptMarker") -export default class ReadReceiptMarker extends React.PureComponent { - static propTypes = { - // the RoomMember to show the RR for - member: PropTypes.object, - // userId to fallback the avatar to - // if the member hasn't been loaded yet - fallbackUserId: PropTypes.string.isRequired, - - // number of pixels to offset the avatar from the right of its parent; - // typically a negative value. - leftOffset: PropTypes.number, - - // true to hide the avatar (it will still be animated) - hidden: PropTypes.bool, - - // don't animate this RR into position - suppressAnimation: PropTypes.bool, - - // an opaque object for storing information about this user's RR in - // this room - readReceiptInfo: PropTypes.object, - - // A function which is used to check if the parent panel is being - // unmounted, to avoid unnecessary work. Should return true if we - // are being unmounted. - checkUnmounting: PropTypes.func, - - // callback for clicks on this RR - onClick: PropTypes.func, - - // Timestamp when the receipt was read - timestamp: PropTypes.number, - - // True to show twelve hour format, false otherwise - showTwelveHour: PropTypes.bool, - }; +export default class ReadReceiptMarker extends React.PureComponent<IProps, IState> { + private avatar: React.RefObject<HTMLDivElement> = createRef(); static defaultProps = { leftOffset: 0, }; - constructor(props) { + constructor(props: IProps) { super(props); - this._avatar = createRef(); - 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 @@ -80,7 +92,7 @@ export default class ReadReceiptMarker extends React.PureComponent { }; } - componentWillUnmount() { + public componentWillUnmount(): void { // 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; @@ -95,29 +107,29 @@ export default class ReadReceiptMarker extends React.PureComponent { return; } - const avatarNode = this._avatar.current; + const avatarNode = this.avatar.current; rrInfo.top = avatarNode.offsetTop; rrInfo.left = avatarNode.offsetLeft; rrInfo.parent = avatarNode.offsetParent; } - componentDidMount() { + public componentDidMount(): void { if (!this.state.suppressDisplay) { // we've already done our display - nothing more to do. return; } - this._animateMarker(); + this.animateMarker(); } - componentDidUpdate(prevProps) { + public componentDidUpdate(prevProps: IProps): void { const differentLeftOffset = prevProps.leftOffset !== this.props.leftOffset; const visibilityChanged = prevProps.hidden !== this.props.hidden; if (differentLeftOffset || visibilityChanged) { - this._animateMarker(); + this.animateMarker(); } } - _animateMarker() { + private animateMarker(): void { // treat new RRs as though they were off the top of the screen let oldTop = -15; @@ -126,7 +138,7 @@ export default class ReadReceiptMarker extends React.PureComponent { oldTop = oldInfo.top + oldInfo.parent.getBoundingClientRect().top; } - const newElement = this._avatar.current; + const newElement = this.avatar.current; let startTopOffset; if (!newElement.offsetParent) { // this seems to happen sometimes for reasons I don't understand @@ -156,10 +168,10 @@ export default class ReadReceiptMarker extends React.PureComponent { }); } - render() { + public render(): JSX.Element { const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); if (this.state.suppressDisplay) { - return <div ref={this._avatar} />; + return <div ref={this.avatar} />; } const style = { @@ -198,7 +210,7 @@ export default class ReadReceiptMarker extends React.PureComponent { style={style} title={title} onClick={this.props.onClick} - inputRef={this._avatar} + inputRef={this.avatar} /> </NodeAnimator> ); From 7290a65924b830868ecb7c0bf712d2a1462514c3 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 10:36:04 +0200 Subject: [PATCH 03/23] Migrate RoomDetailList to TypeScript --- .../{RoomDetailList.js => RoomDetailList.tsx} | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) rename src/components/views/rooms/{RoomDetailList.js => RoomDetailList.tsx} (81%) diff --git a/src/components/views/rooms/RoomDetailList.js b/src/components/views/rooms/RoomDetailList.tsx similarity index 81% rename from src/components/views/rooms/RoomDetailList.js rename to src/components/views/rooms/RoomDetailList.tsx index bf2f5418c9..ee7383d7c7 100644 --- a/src/components/views/rooms/RoomDetailList.js +++ b/src/components/views/rooms/RoomDetailList.tsx @@ -14,24 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. */ +import React from 'react'; +import { Room } from 'matrix-js-sdk/src'; +import classNames from 'classnames'; import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; -import React from 'react'; import { _t } from '../../../languageHandler'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { roomShape } from './RoomDetailRow'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -@replaceableComponent("views.rooms.RoomDetailList") -export default class RoomDetailList extends React.Component { - static propTypes = { - rooms: PropTypes.arrayOf(roomShape), - className: PropTypes.string, - }; +interface IProps { + rooms?: Room[]; + className?: string; +} - getRows() { +@replaceableComponent("views.rooms.RoomDetailList") +export default class RoomDetailList extends React.Component<IProps> { + public getRows(): JSX.Element[] { if (!this.props.rooms) return []; const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow'); @@ -40,15 +39,15 @@ export default class RoomDetailList extends React.Component { }); } - onDetailsClick = (ev, room) => { + public onDetailsClick = (ev: React.MouseEvent, room: Room): void => { dis.dispatch({ action: 'view_room', room_id: room.roomId, - room_alias: room.canonicalAlias || (room.aliases || [])[0], + room_alias: room.getCanonicalAlias() || (room.getAltAliases() || [])[0], }); }; - render() { + public render(): JSX.Element { const rows = this.getRows(); let rooms; if (rows.length === 0) { From 7e4c88f6ba11d610e4930daf8eab0cc3f1698738 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 10:46:38 +0200 Subject: [PATCH 04/23] Migrate RoomUpgradeWarningBar to TypeScript --- ...arningBar.js => RoomUpgradeWarningBar.tsx} | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) rename src/components/views/rooms/{RoomUpgradeWarningBar.js => RoomUpgradeWarningBar.tsx} (88%) diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.js b/src/components/views/rooms/RoomUpgradeWarningBar.tsx similarity index 88% rename from src/components/views/rooms/RoomUpgradeWarningBar.js rename to src/components/views/rooms/RoomUpgradeWarningBar.tsx index 384845cdf9..6706e248e0 100644 --- a/src/components/views/rooms/RoomUpgradeWarningBar.js +++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx @@ -1,5 +1,5 @@ /* -Copyright 2018-2020 New Vector Ltd +Copyright 2018-2021 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. @@ -15,7 +15,7 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; +import { MatrixEvent, Room, RoomState } from 'matrix-js-sdk/src'; import * as sdk from '../../../index'; import Modal from '../../../Modal'; @@ -23,33 +23,31 @@ import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + room: Room; +} + +interface IState { + upgraded?: boolean; +} + @replaceableComponent("views.rooms.RoomUpgradeWarningBar") -export default class RoomUpgradeWarningBar extends React.PureComponent { - static propTypes = { - room: PropTypes.object.isRequired, - recommendation: PropTypes.object.isRequired, - }; - - constructor(props) { - super(props); - this.state = {}; - } - - componentDidMount() { +export default class RoomUpgradeWarningBar extends React.PureComponent<IProps, IState> { + public componentDidMount(): void { 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); + MatrixClientPeg.get().on("RoomState.events", this.onStateEvents); } - componentWillUnmount() { + public componentWillUnmount(): void { const cli = MatrixClientPeg.get(); if (cli) { - cli.removeListener("RoomState.events", this._onStateEvents); + cli.removeListener("RoomState.events", this.onStateEvents); } } - _onStateEvents = (event, state) => { + private onStateEvents = (event: MatrixEvent, state: RoomState): void => { if (!this.props.room || event.getRoomId() !== this.props.room.roomId) { return; } @@ -60,12 +58,12 @@ export default class RoomUpgradeWarningBar extends React.PureComponent { this.setState({ upgraded: tombstone && tombstone.getContent().replacement_room }); }; - onUpgradeClick = () => { + private onUpgradeClick = (): void => { const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog'); Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, { room: this.props.room }); }; - render() { + public render(): JSX.Element { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); let doUpgradeWarnings = ( From eb120901aea7c1fb5b54bb9ceb8cf7516a7498d7 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 10:48:30 +0200 Subject: [PATCH 05/23] Migrate SimpleRoomHeader to TypeScript --- ...mpleRoomHeader.js => SimpleRoomHeader.tsx} | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) rename src/components/views/rooms/{SimpleRoomHeader.js => SimpleRoomHeader.tsx} (84%) diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.tsx similarity index 84% rename from src/components/views/rooms/SimpleRoomHeader.js rename to src/components/views/rooms/SimpleRoomHeader.tsx index a2b5566e39..b81e906559 100644 --- a/src/components/views/rooms/SimpleRoomHeader.js +++ b/src/components/views/rooms/SimpleRoomHeader.tsx @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd +Copyright 2021 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. @@ -15,23 +16,21 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + title?: string; + // `src` to an image. Optional. + icon?: string; +} + /* * A stripped-down room header used for things like the user settings * and room directory. */ @replaceableComponent("views.rooms.SimpleRoomHeader") -export default class SimpleRoomHeader extends React.Component { - static propTypes = { - title: PropTypes.string, - - // `src` to an image. Optional. - icon: PropTypes.string, - }; - - render() { +export default class SimpleRoomHeader extends React.PureComponent<IProps> { + public render(): JSX.Element { let icon; if (this.props.icon) { icon = <img From c56d26731658777d6618ffbb22e80e1234064406 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 10:51:08 +0200 Subject: [PATCH 06/23] Migrate TopUnreadMessagesBar to TypeScript --- ...dMessagesBar.js => TopUnreadMessagesBar.tsx} | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) rename src/components/views/rooms/{TopUnreadMessagesBar.js => TopUnreadMessagesBar.tsx} (84%) diff --git a/src/components/views/rooms/TopUnreadMessagesBar.js b/src/components/views/rooms/TopUnreadMessagesBar.tsx similarity index 84% rename from src/components/views/rooms/TopUnreadMessagesBar.js rename to src/components/views/rooms/TopUnreadMessagesBar.tsx index d2a3e3a303..01797299cf 100644 --- a/src/components/views/rooms/TopUnreadMessagesBar.js +++ b/src/components/views/rooms/TopUnreadMessagesBar.tsx @@ -1,7 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd -Copyright 2019 New Vector Ltd +Copyright 2019-2021 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,19 +17,18 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import AccessibleButton from '../elements/AccessibleButton'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -@replaceableComponent("views.rooms.TopUnreadMessagesBar") -export default class TopUnreadMessagesBar extends React.Component { - static propTypes = { - onScrollUpClick: PropTypes.func, - onCloseClick: PropTypes.func, - }; +interface IProps { + onScrollUpClick?: (e: React.MouseEvent) => void; + onCloseClick?: (e: React.MouseEvent) => void; +} - render() { +@replaceableComponent("views.rooms.TopUnreadMessagesBar") +export default class TopUnreadMessagesBar extends React.PureComponent<IProps> { + public render(): JSX.Element { return ( <div className="mx_TopUnreadMessagesBar"> <AccessibleButton From 1f55158727edbe50cc71d51f3a27e89bbd7b90ff Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 10:53:53 +0200 Subject: [PATCH 07/23] Migrate AvatarSetting to TypeScript --- .../{AvatarSetting.js => AvatarSetting.tsx} | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) rename src/components/views/settings/{AvatarSetting.js => AvatarSetting.tsx} (86%) diff --git a/src/components/views/settings/AvatarSetting.js b/src/components/views/settings/AvatarSetting.tsx similarity index 86% rename from src/components/views/settings/AvatarSetting.js rename to src/components/views/settings/AvatarSetting.tsx index f22c4f1c85..806d0adb73 100644 --- a/src/components/views/settings/AvatarSetting.js +++ b/src/components/views/settings/AvatarSetting.tsx @@ -15,12 +15,19 @@ limitations under the License. */ import React, { useState } from "react"; -import PropTypes from "prop-types"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; import classNames from "classnames"; -const AvatarSetting = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar }) => { +interface IProps { + avatarUrl?: string; + avatarName: string; // name of user/room the avatar belongs to + uploadAvatar?: (e: React.MouseEvent) => void; + removeAvatar?: (e: React.MouseEvent) => void; + avatarAltText: string; +} + +const AvatarSetting: React.FC<IProps> = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar }) => { const [isHovering, setIsHovering] = useState(false); const hoveringProps = { onMouseEnter: () => setIsHovering(true), @@ -78,12 +85,4 @@ const AvatarSetting = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, rem </div>; }; -AvatarSetting.propTypes = { - avatarUrl: PropTypes.string, - avatarName: PropTypes.string.isRequired, // name of user/room the avatar belongs to - uploadAvatar: PropTypes.func, - removeAvatar: PropTypes.func, - avatarAltText: PropTypes.string.isRequired, -}; - export default AvatarSetting; From bedfbedff0ec3ffd8e081eef91951b76702ccf65 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 11:04:36 +0200 Subject: [PATCH 08/23] Migrate ChangeAvatar to TypeScript --- .../{ChangeAvatar.js => ChangeAvatar.tsx} | 96 ++++++++++--------- 1 file changed, 52 insertions(+), 44 deletions(-) rename src/components/views/settings/{ChangeAvatar.js => ChangeAvatar.tsx} (74%) diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.tsx similarity index 74% rename from src/components/views/settings/ChangeAvatar.js rename to src/components/views/settings/ChangeAvatar.tsx index c3a1544cdc..6394bad3de 100644 --- a/src/components/views/settings/ChangeAvatar.js +++ b/src/components/views/settings/ChangeAvatar.tsx @@ -1,5 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd +Copyright 2021 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. @@ -15,54 +16,62 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Spinner from '../elements/Spinner'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import { MatrixEvent, Room } from '../../../../../matrix-js-sdk/src'; + +interface IProps { + initialAvatarUrl?: string; + room?: Room; + // if false, you need to call changeAvatar.onFileSelected yourself. + showUploadSection?: boolean; + width?: number; + height?: number; + className?: string; +} + +interface IState { + avatarUrl?: string; + errorText?: string; + phase?: Phases; +} + +enum Phases { + Display = "display", + Uploading = "uploading", + Error = "error", +} @replaceableComponent("views.settings.ChangeAvatar") -export default class ChangeAvatar extends React.Component { - static propTypes = { - initialAvatarUrl: PropTypes.string, - room: PropTypes.object, - // if false, you need to call changeAvatar.onFileSelected yourself. - showUploadSection: PropTypes.bool, - width: PropTypes.number, - height: PropTypes.number, - className: PropTypes.string, - }; - - static Phases = { - Display: "display", - Uploading: "uploading", - Error: "error", - }; - - static defaultProps = { +export default class ChangeAvatar extends React.Component<IProps, IState> { + public static defaultProps = { showUploadSection: true, className: "", width: 80, height: 80, }; - constructor(props) { + private avatarSet = false; + + constructor(props: IProps) { super(props); this.state = { avatarUrl: this.props.initialAvatarUrl, - phase: ChangeAvatar.Phases.Display, + phase: Phases.Display, }; } - componentDidMount() { + public componentDidMount(): void { MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents); } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase + public UNSAFE_componentWillReceiveProps(newProps): void { // eslint-disable-line camelcase if (this.avatarSet) { // don't clobber what the user has just set return; @@ -72,13 +81,13 @@ export default class ChangeAvatar extends React.Component { }); } - componentWillUnmount() { + public componentWillUnmount(): void { if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents); } } - onRoomStateEvents = (ev) => { + public onRoomStateEvents = (ev: MatrixEvent) => { if (!this.props.room) { return; } @@ -94,18 +103,17 @@ export default class ChangeAvatar extends React.Component { } }; - setAvatarFromFile(file) { + public setAvatarFromFile(file): Promise<{}> { let newUrl = null; this.setState({ - phase: ChangeAvatar.Phases.Uploading, + phase: Phases.Uploading, }); - const self = this; - const httpPromise = MatrixClientPeg.get().uploadContent(file).then(function(url) { + const httpPromise = MatrixClientPeg.get().uploadContent(file).then((url) => { newUrl = url; - if (self.props.room) { + if (this.props.room) { return MatrixClientPeg.get().sendStateEvent( - self.props.room.roomId, + this.props.room.roomId, 'm.room.avatar', { url: url }, '', @@ -115,33 +123,33 @@ export default class ChangeAvatar extends React.Component { } }); - httpPromise.then(function() { - self.setState({ - phase: ChangeAvatar.Phases.Display, + httpPromise.then(() => { + this.setState({ + phase: Phases.Display, avatarUrl: mediaFromMxc(newUrl).srcHttp, }); - }, function(error) { - self.setState({ - phase: ChangeAvatar.Phases.Error, + }, () => { + this.setState({ + phase: Phases.Error, }); - self.onError(error); + this.onError(); }); return httpPromise; } - onFileSelected = (ev) => { + private onFileSelected = (ev: React.ChangeEvent<HTMLInputElement>) => { this.avatarSet = true; return this.setAvatarFromFile(ev.target.files[0]); }; - onError = (error) => { + private onError = (): void => { this.setState({ errorText: _t("Failed to upload profile picture!"), }); }; - render() { + public render(): JSX.Element { let avatarImg; // Having just set an avatar we just display that since it will take a little // time to propagate through to the RoomAvatar. @@ -178,8 +186,8 @@ export default class ChangeAvatar extends React.Component { } switch (this.state.phase) { - case ChangeAvatar.Phases.Display: - case ChangeAvatar.Phases.Error: + case Phases.Display: + case Phases.Error: return ( <div> <div className={this.props.className}> @@ -188,7 +196,7 @@ export default class ChangeAvatar extends React.Component { { uploadSection } </div> ); - case ChangeAvatar.Phases.Uploading: + case Phases.Uploading: return ( <Spinner /> ); From 1e431057ff62f9511181c457652c2ed3b8aa826f Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 11:06:34 +0200 Subject: [PATCH 09/23] Migrate ChangeDisplayName to TypeScript --- ...hangeDisplayName.js => ChangeDisplayName.tsx} | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename src/components/views/settings/{ChangeDisplayName.js => ChangeDisplayName.tsx} (81%) diff --git a/src/components/views/settings/ChangeDisplayName.js b/src/components/views/settings/ChangeDisplayName.tsx similarity index 81% rename from src/components/views/settings/ChangeDisplayName.js rename to src/components/views/settings/ChangeDisplayName.tsx index 2f336e18c6..09c9ecc23e 100644 --- a/src/components/views/settings/ChangeDisplayName.js +++ b/src/components/views/settings/ChangeDisplayName.tsx @@ -1,6 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd -Copyright 2018 New Vector Ltd +Copyright 2018 - 2021 New Vector Ltd Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; @replaceableComponent("views.settings.ChangeDisplayName") export default class ChangeDisplayName extends React.Component { - _getDisplayName = async () => { + private getDisplayName = async (): Promise<string> => { const cli = MatrixClientPeg.get(); try { const res = await cli.getProfileInfo(cli.getUserId()); @@ -34,21 +34,21 @@ export default class ChangeDisplayName extends React.Component { } }; - _changeDisplayName = (newDisplayname) => { + private changeDisplayName = (newDisplayname: string): Promise<{}> => { const cli = MatrixClientPeg.get(); - return cli.setDisplayName(newDisplayname).catch(function(e) { - throw new Error("Failed to set display name", e); + return cli.setDisplayName(newDisplayname).catch(function() { + throw new Error("Failed to set display name"); }); }; - render() { + public render(): JSX.Element { const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer'); return ( <EditableTextContainer - getInitialValue={this._getDisplayName} + getInitialValue={this.getDisplayName} placeholder={_t("No display name")} blurToSubmit={true} - onSubmit={this._changeDisplayName} /> + onSubmit={this.changeDisplayName} /> ); } } From fb6a6370e7e37c3c5eef48c3208b3cb9d5915b22 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 11:17:19 +0200 Subject: [PATCH 10/23] Migrate DevicesPanel to TypeScript --- .../{DevicesPanel.js => DevicesPanel.tsx} | 89 +++++++++---------- 1 file changed, 41 insertions(+), 48 deletions(-) rename src/components/views/settings/{DevicesPanel.js => DevicesPanel.tsx} (82%) diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.tsx similarity index 82% rename from src/components/views/settings/DevicesPanel.js rename to src/components/views/settings/DevicesPanel.tsx index 0f052332ee..6fd7f90c8e 100644 --- a/src/components/views/settings/DevicesPanel.js +++ b/src/components/views/settings/DevicesPanel.tsx @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2021 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. @@ -16,8 +17,8 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; +import { IMyDevice } from "matrix-js-sdk/src/client"; import * as sdk from '../../../index'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; @@ -26,42 +27,37 @@ import Modal from '../../../Modal'; import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + className?: string; +} + +interface IState { + devices: IMyDevice[]; + deviceLoadError?: string; + selectedDevices?: string[]; + deleting?: boolean; +} + @replaceableComponent("views.settings.DevicesPanel") -export default class DevicesPanel extends React.Component { - constructor(props) { - super(props); +export default class DevicesPanel extends React.Component<IProps, IState> { + private unmounted = false; - this.state = { - devices: undefined, - deviceLoadError: undefined, - - selectedDevices: [], - deleting: false, - }; - - this._unmounted = false; - - this._renderDevice = this._renderDevice.bind(this); - this._onDeviceSelectionToggled = this._onDeviceSelectionToggled.bind(this); - this._onDeleteClick = this._onDeleteClick.bind(this); + public componentDidMount(): void { + this.loadDevices(); } - componentDidMount() { - this._loadDevices(); + public componentWillUnmount(): void { + this.unmounted = true; } - componentWillUnmount() { - this._unmounted = true; - } - - _loadDevices() { + private loadDevices(): void { MatrixClientPeg.get().getDevices().then( (resp) => { - if (this._unmounted) { return; } + if (this.unmounted) { return; } this.setState({ devices: resp.devices || [] }); }, (error) => { - if (this._unmounted) { return; } + if (this.unmounted) { return; } let errtxt; if (error.httpStatus == 404) { // 404 probably means the HS doesn't yet support the API. @@ -79,7 +75,7 @@ export default class DevicesPanel extends React.Component { * compare two devices, sorting from most-recently-seen to least-recently-seen * (and then, for stability, by device id) */ - _deviceCompare(a, b) { + private deviceCompare(a: IMyDevice, b: IMyDevice): number { // return < 0 if a comes before b, > 0 if a comes after b. const lastSeenDelta = (b.last_seen_ts || 0) - (a.last_seen_ts || 0); @@ -91,8 +87,8 @@ export default class DevicesPanel extends React.Component { return (idA < idB) ? -1 : (idA > idB) ? 1 : 0; } - _onDeviceSelectionToggled(device) { - if (this._unmounted) { return; } + private onDeviceSelectionToggled = (device: IMyDevice): void => { + if (this.unmounted) { return; } const deviceId = device.device_id; this.setState((state, props) => { @@ -108,15 +104,15 @@ export default class DevicesPanel extends React.Component { return { selectedDevices }; }); - } + }; - _onDeleteClick() { + private onDeleteClick = (): void => { this.setState({ deleting: true, }); - this._makeDeleteRequest(null).catch((error) => { - if (this._unmounted) { return; } + this.makeDeleteRequest(null).catch((error) => { + if (this.unmounted) { return; } if (error.httpStatus !== 401 || !error.data || !error.data.flows) { // doesn't look like an interactive-auth failure throw error; @@ -148,7 +144,7 @@ export default class DevicesPanel extends React.Component { title: _t("Authentication"), matrixClient: MatrixClientPeg.get(), authData: error.data, - makeRequest: this._makeDeleteRequest.bind(this), + makeRequest: this.makeDeleteRequest.bind(this), aestheticsForStagePhases: { [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, @@ -156,15 +152,16 @@ export default class DevicesPanel extends React.Component { }); }).catch((e) => { console.error("Error deleting sessions", e); - if (this._unmounted) { return; } + if (this.unmounted) { return; } }).finally(() => { this.setState({ deleting: false, }); }); - } + }; - _makeDeleteRequest(auth) { + // TODO: proper typing for auth + private makeDeleteRequest(auth?: any): Promise<any> { return MatrixClientPeg.get().deleteMultipleDevices(this.state.selectedDevices, auth).then( () => { // Remove the deleted devices from `devices`, reset selection to [] @@ -178,17 +175,17 @@ export default class DevicesPanel extends React.Component { ); } - _renderDevice(device) { + private renderDevice = (device: IMyDevice): JSX.Element => { const DevicesPanelEntry = sdk.getComponent('settings.DevicesPanelEntry'); return <DevicesPanelEntry key={device.device_id} device={device} selected={this.state.selectedDevices.includes(device.device_id)} - onDeviceToggled={this._onDeviceSelectionToggled} + onDeviceToggled={this.onDeviceSelectionToggled} />; - } + }; - render() { + public render(): JSX.Element { const Spinner = sdk.getComponent("elements.Spinner"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); @@ -208,11 +205,11 @@ export default class DevicesPanel extends React.Component { return <Spinner className={classes} />; } - devices.sort(this._deviceCompare); + devices.sort(this.deviceCompare); const deleteButton = this.state.deleting ? <Spinner w={22} h={22} /> : - <AccessibleButton onClick={this._onDeleteClick} kind="danger_sm"> + <AccessibleButton onClick={this.onDeleteClick} kind="danger_sm"> { _t("Delete %(count)s sessions", { count: this.state.selectedDevices.length }) } </AccessibleButton>; @@ -227,12 +224,8 @@ export default class DevicesPanel extends React.Component { { this.state.selectedDevices.length > 0 ? deleteButton : null } </div> </div> - { devices.map(this._renderDevice) } + { devices.map(this.renderDevice) } </div> ); } } - -DevicesPanel.propTypes = { - className: PropTypes.string, -}; From dfd986751ffdd810e5d54b7fa95d4470ebbca77e Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 11:21:59 +0200 Subject: [PATCH 11/23] Migrate DevicesPanelEntry to TypeScript --- ...cesPanelEntry.js => DevicesPanelEntry.tsx} | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) rename src/components/views/settings/{DevicesPanelEntry.js => DevicesPanelEntry.tsx} (78%) diff --git a/src/components/views/settings/DevicesPanelEntry.js b/src/components/views/settings/DevicesPanelEntry.tsx similarity index 78% rename from src/components/views/settings/DevicesPanelEntry.js rename to src/components/views/settings/DevicesPanelEntry.tsx index a5b674b8f6..d44147f591 100644 --- a/src/components/views/settings/DevicesPanelEntry.js +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd +Copyright 2021 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. @@ -15,7 +16,7 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; +import { IMyDevice } from 'matrix-js-sdk/src'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -24,21 +25,19 @@ import { formatDate } from '../../../DateUtils'; import StyledCheckbox from '../elements/StyledCheckbox'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +interface IProps { + device?: IMyDevice; + onDeviceToggled?: (device: IMyDevice) => void; + selected?: boolean; +} + @replaceableComponent("views.settings.DevicesPanelEntry") -export default class DevicesPanelEntry extends React.Component { - constructor(props) { - super(props); +export default class DevicesPanelEntry extends React.Component<IProps> { + public static defaultProps = { + onDeviceToggled: () => {}, + }; - this._unmounted = false; - this.onDeviceToggled = this.onDeviceToggled.bind(this); - this._onDisplayNameChanged = this._onDisplayNameChanged.bind(this); - } - - componentWillUnmount() { - this._unmounted = true; - } - - _onDisplayNameChanged(value) { + private onDisplayNameChanged = (value: string): Promise<{}> => { const device = this.props.device; return MatrixClientPeg.get().setDeviceDetails(device.device_id, { display_name: value, @@ -46,13 +45,13 @@ export default class DevicesPanelEntry extends React.Component { console.error("Error setting session display name", e); throw new Error(_t("Failed to set display name")); }); - } + }; - onDeviceToggled() { + private onDeviceToggled = (): void => { this.props.onDeviceToggled(this.props.device); - } + }; - render() { + public render(): JSX.Element { const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer'); const device = this.props.device; @@ -76,7 +75,7 @@ export default class DevicesPanelEntry extends React.Component { </div> <div className="mx_DevicesPanel_deviceName"> <EditableTextContainer initialValue={device.display_name} - onSubmit={this._onDisplayNameChanged} + onSubmit={this.onDisplayNameChanged} placeholder={device.device_id} /> </div> @@ -90,12 +89,3 @@ export default class DevicesPanelEntry extends React.Component { ); } } - -DevicesPanelEntry.propTypes = { - device: PropTypes.object.isRequired, - onDeviceToggled: PropTypes.func, -}; - -DevicesPanelEntry.defaultProps = { - onDeviceToggled: function() {}, -}; From 447beb829458104d2c29adb08751544a1433d4ea Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 11:27:17 +0200 Subject: [PATCH 12/23] Migrate IntegrationManager to TypeScript --- ...ationManager.js => IntegrationManager.tsx} | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) rename src/components/views/settings/{IntegrationManager.js => IntegrationManager.tsx} (72%) diff --git a/src/components/views/settings/IntegrationManager.js b/src/components/views/settings/IntegrationManager.tsx similarity index 72% rename from src/components/views/settings/IntegrationManager.js rename to src/components/views/settings/IntegrationManager.tsx index 9f2985df14..f43fb55004 100644 --- a/src/components/views/settings/IntegrationManager.js +++ b/src/components/views/settings/IntegrationManager.tsx @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2021 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. @@ -16,53 +17,55 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher/dispatcher'; import { Key } from "../../../Keyboard"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { ActionPayload } from '../../../dispatcher/payloads'; + +interface IProps { + // false to display an error saying that we couldn't connect to the integration manager + connected: boolean; + + // true to display a loading spinner + loading: boolean; + + // The source URL to load + url?: string; + + // callback when the manager is dismissed + onFinished: () => void; +} + +interface IState { + errored: boolean; +} @replaceableComponent("views.settings.IntegrationManager") -export default class IntegrationManager extends React.Component { - static propTypes = { - // false to display an error saying that we couldn't connect to the integration manager - connected: PropTypes.bool.isRequired, +export default class IntegrationManager extends React.Component<IProps, IState> { + private dispatcherRef: string; - // true to display a loading spinner - loading: PropTypes.bool.isRequired, - - // The source URL to load - url: PropTypes.string, - - // callback when the manager is dismissed - onFinished: PropTypes.func.isRequired, - }; - - static defaultProps = { + public static defaultProps = { connected: true, loading: false, }; - constructor(props) { - super(props); + public state = { + errored: false, + }; - this.state = { - errored: false, - }; - } - - componentDidMount() { + public componentDidMount(): void { this.dispatcherRef = dis.register(this.onAction); document.addEventListener("keydown", this.onKeyDown); } - componentWillUnmount() { + public componentWillUnmount(): void { dis.unregister(this.dispatcherRef); document.removeEventListener("keydown", this.onKeyDown); } - onKeyDown = (ev) => { + private onKeyDown = (ev: KeyboardEvent): void => { if (ev.key === Key.ESCAPE) { ev.stopPropagation(); ev.preventDefault(); @@ -70,17 +73,17 @@ export default class IntegrationManager extends React.Component { } }; - onAction = (payload) => { + private onAction = (payload: ActionPayload): void => { if (payload.action === 'close_scalar') { this.props.onFinished(); } }; - onError = () => { + private onError = (): void => { this.setState({ errored: true }); }; - render() { + public render(): JSX.Element { if (this.props.loading) { const Spinner = sdk.getComponent("elements.Spinner"); return ( From 2e1d5aa67bd1688544c26d2180c89a467c87807f Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Sat, 14 Aug 2021 11:36:12 +0200 Subject: [PATCH 13/23] Migrate ProfileSettings to TypeScript --- ...ProfileSettings.js => ProfileSettings.tsx} | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) rename src/components/views/settings/{ProfileSettings.js => ProfileSettings.tsx} (83%) diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.tsx similarity index 83% rename from src/components/views/settings/ProfileSettings.js rename to src/components/views/settings/ProfileSettings.tsx index d05fca983c..888ff8967b 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.tsx @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2019-2021 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. @@ -26,10 +26,25 @@ import ErrorDialog from "../dialogs/ErrorDialog"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +interface IProps { + +} + +interface IState { + userId?: string; + originalDisplayName?: string; + displayName?: string; + originalAvatarUrl?: string; + avatarUrl?: string | ArrayBuffer; + avatarFile?: File; + enableProfileSave?: boolean; +} + @replaceableComponent("views.settings.ProfileSettings") -export default class ProfileSettings extends React.Component { - constructor() { - super(); +export default class ProfileSettings extends React.Component<IProps, IState> { + private avatarUpload: React.RefObject<HTMLInputElement> = createRef(); + constructor(props: IProps) { + super(props); const client = MatrixClientPeg.get(); let avatarUrl = OwnProfileStore.instance.avatarMxc; @@ -43,17 +58,15 @@ export default class ProfileSettings extends React.Component { avatarFile: null, enableProfileSave: false, }; - - this._avatarUpload = createRef(); } - _uploadAvatar = () => { - this._avatarUpload.current.click(); + private uploadAvatar = (): void => { + this.avatarUpload.current.click(); }; - _removeAvatar = () => { + private removeAvatar = (): void => { // clear file upload field so same file can be selected - this._avatarUpload.current.value = ""; + this.avatarUpload.current.value = ""; this.setState({ avatarUrl: null, avatarFile: null, @@ -61,7 +74,7 @@ export default class ProfileSettings extends React.Component { }); }; - _cancelProfileChanges = async (e) => { + private cancelProfileChanges = async (e: React.MouseEvent): Promise<void> => { e.stopPropagation(); e.preventDefault(); @@ -74,7 +87,7 @@ export default class ProfileSettings extends React.Component { }); }; - _saveProfile = async (e) => { + private saveProfile = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => { e.stopPropagation(); e.preventDefault(); @@ -82,7 +95,7 @@ export default class ProfileSettings extends React.Component { this.setState({ enableProfileSave: false }); const client = MatrixClientPeg.get(); - const newState = {}; + const newState: IState = {}; const displayName = this.state.displayName.trim(); try { @@ -115,14 +128,14 @@ export default class ProfileSettings extends React.Component { this.setState(newState); }; - _onDisplayNameChanged = (e) => { + private onDisplayNameChanged = (e: React.ChangeEvent<HTMLInputElement>): void => { this.setState({ displayName: e.target.value, enableProfileSave: true, }); }; - _onAvatarChanged = (e) => { + private onAvatarChanged = (e: React.ChangeEvent<HTMLInputElement>): void => { if (!e.target.files || !e.target.files.length) { this.setState({ avatarUrl: this.state.originalAvatarUrl, @@ -144,7 +157,7 @@ export default class ProfileSettings extends React.Component { reader.readAsDataURL(file); }; - render() { + public render(): JSX.Element { const hostingSignupLink = getHostingLink('user-settings'); let hostingSignup = null; if (hostingSignupLink) { @@ -165,16 +178,16 @@ export default class ProfileSettings extends React.Component { const AvatarSetting = sdk.getComponent('settings.AvatarSetting'); return ( <form - onSubmit={this._saveProfile} + onSubmit={this.saveProfile} autoComplete="off" noValidate={true} className="mx_ProfileSettings_profileForm" > <input type="file" - ref={this._avatarUpload} + ref={this.avatarUpload} className="mx_ProfileSettings_avatarUpload" - onChange={this._onAvatarChanged} + onChange={this.onAvatarChanged} accept="image/*" /> <div className="mx_ProfileSettings_profile"> @@ -185,7 +198,7 @@ export default class ProfileSettings extends React.Component { type="text" value={this.state.displayName} autoComplete="off" - onChange={this._onDisplayNameChanged} + onChange={this.onDisplayNameChanged} /> <p> { this.state.userId } @@ -196,19 +209,19 @@ export default class ProfileSettings extends React.Component { avatarUrl={this.state.avatarUrl} avatarName={this.state.displayName || this.state.userId} avatarAltText={_t("Profile picture")} - uploadAvatar={this._uploadAvatar} - removeAvatar={this._removeAvatar} /> + uploadAvatar={this.uploadAvatar} + removeAvatar={this.removeAvatar} /> </div> <div className="mx_ProfileSettings_buttons"> <AccessibleButton - onClick={this._cancelProfileChanges} + onClick={this.cancelProfileChanges} kind="link" disabled={!this.state.enableProfileSave} > { _t("Cancel") } </AccessibleButton> <AccessibleButton - onClick={this._saveProfile} + onClick={this.saveProfile} kind="primary" disabled={!this.state.enableProfileSave} > From 800b3f1424d1aa24e6fa5663968fb6b65dc521ab Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Mon, 16 Aug 2021 09:16:02 +0100 Subject: [PATCH 14/23] Fix linter --- src/components/structures/RoomView.tsx | 3 +-- src/components/views/settings/ChangeAvatar.tsx | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 474b99262d..31aa8d50fa 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1867,7 +1867,7 @@ export default class RoomView extends React.Component<IProps, IState> { isRoomEncrypted={this.context.isRoomEncrypted(this.state.room.roomId)} />; } else if (showRoomUpgradeBar) { - aux = <RoomUpgradeWarningBar room={this.state.room} recommendation={roomVersionRecommendation} />; + aux = <RoomUpgradeWarningBar room={this.state.room} />; } else if (myMembership !== "join") { // We do have a room object for this room, but we're not currently in it. // We may have a 3rd party invite to it. @@ -2042,7 +2042,6 @@ export default class RoomView extends React.Component<IProps, IState> { highlight={this.state.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0} numUnreadMessages={this.state.numUnreadMessages} onScrollToBottomClick={this.jumpToLiveTimeline} - roomId={this.state.roomId} />); } diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx index 6394bad3de..92ff34fbcb 100644 --- a/src/components/views/settings/ChangeAvatar.tsx +++ b/src/components/views/settings/ChangeAvatar.tsx @@ -71,7 +71,8 @@ export default class ChangeAvatar extends React.Component<IProps, IState> { } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - public UNSAFE_componentWillReceiveProps(newProps): void { // eslint-disable-line camelcase + // eslint-disable-next-line + public UNSAFE_componentWillReceiveProps(newProps): void { if (this.avatarSet) { // don't clobber what the user has just set return; From 02ece401031092e3e08ae5e410c53a09048aaba2 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Mon, 16 Aug 2021 09:19:58 +0100 Subject: [PATCH 15/23] Fix import path on ChangeAvatar --- src/components/views/settings/ChangeAvatar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx index 92ff34fbcb..7a3e639876 100644 --- a/src/components/views/settings/ChangeAvatar.tsx +++ b/src/components/views/settings/ChangeAvatar.tsx @@ -16,13 +16,13 @@ limitations under the License. */ import React from 'react'; +import { MatrixEvent, Room } from 'matrix-js-sdk/src'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Spinner from '../elements/Spinner'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; -import { MatrixEvent, Room } from '../../../../../matrix-js-sdk/src'; interface IProps { initialAvatarUrl?: string; From 617e7deff54f21b7e8ad62ad2bd563a50bb22225 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germain@souquet.com> Date: Tue, 17 Aug 2021 18:05:10 +0100 Subject: [PATCH 16/23] replace sdk.getComponent with import statements --- src/components/views/rooms/ReadReceiptMarker.tsx | 4 ++-- src/components/views/rooms/RoomDetailList.tsx | 6 ++---- src/components/views/rooms/RoomUpgradeWarningBar.tsx | 6 ++---- src/components/views/settings/ChangeAvatar.tsx | 5 ++--- src/components/views/settings/ChangeDisplayName.tsx | 3 +-- src/components/views/settings/DevicesPanel.tsx | 3 +-- src/components/views/settings/DevicesPanelEntry.tsx | 4 +--- src/components/views/settings/IntegrationManager.tsx | 3 +-- src/components/views/settings/ProfileSettings.tsx | 5 ++--- 9 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx index 11e7563f11..28e1ec85e9 100644 --- a/src/components/views/rooms/ReadReceiptMarker.tsx +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -21,10 +21,11 @@ import { RoomMember } from 'matrix-js-sdk/src'; import { _t } from '../../../languageHandler'; import { formatDate } from '../../../DateUtils'; import NodeAnimator from "../../../NodeAnimator"; -import * as sdk from "../../../index"; import { toPx } from "../../../utils/units"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import MemberAvatar from '../avatars/MemberAvatar'; + interface IProps { // the RoomMember to show the RR for member?: RoomMember; @@ -169,7 +170,6 @@ export default class ReadReceiptMarker extends React.PureComponent<IProps, IStat } public render(): JSX.Element { - const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); if (this.state.suppressDisplay) { return <div ref={this.avatar} />; } diff --git a/src/components/views/rooms/RoomDetailList.tsx b/src/components/views/rooms/RoomDetailList.tsx index ee7383d7c7..cace94ce08 100644 --- a/src/components/views/rooms/RoomDetailList.tsx +++ b/src/components/views/rooms/RoomDetailList.tsx @@ -17,11 +17,11 @@ limitations under the License. import React from 'react'; import { Room } from 'matrix-js-sdk/src'; import classNames from 'classnames'; -import * as sdk from '../../../index'; import dis from '../../../dispatcher/dispatcher'; import { _t } from '../../../languageHandler'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import RoomDetailRow from "./RoomDetailRow"; interface IProps { rooms?: Room[]; @@ -31,9 +31,7 @@ interface IProps { @replaceableComponent("views.rooms.RoomDetailList") export default class RoomDetailList extends React.Component<IProps> { public getRows(): JSX.Element[] { - if (!this.props.rooms) return []; - - const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow'); + if (!this.props.rooms) return []; s; return this.props.rooms.map((room, index) => { return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />; }); diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.tsx b/src/components/views/rooms/RoomUpgradeWarningBar.tsx index 6706e248e0..3380bd5392 100644 --- a/src/components/views/rooms/RoomUpgradeWarningBar.tsx +++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx @@ -16,12 +16,13 @@ limitations under the License. import React from 'react'; import { MatrixEvent, Room, RoomState } from 'matrix-js-sdk/src'; -import * as sdk from '../../../index'; import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import RoomUpgradeDialog from '../dialogs/RoomUpgradeDialog'; +import AccessibleButton from '../elements/AccessibleButton'; interface IProps { room: Room; @@ -59,13 +60,10 @@ export default class RoomUpgradeWarningBar extends React.PureComponent<IProps, I }; private onUpgradeClick = (): void => { - const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog'); Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, { room: this.props.room }); }; public render(): JSX.Element { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - let doUpgradeWarnings = ( <div> <div className="mx_RoomUpgradeWarningBar_body"> diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx index 7a3e639876..7126fe8cc3 100644 --- a/src/components/views/settings/ChangeAvatar.tsx +++ b/src/components/views/settings/ChangeAvatar.tsx @@ -18,11 +18,12 @@ limitations under the License. import React from 'react'; import { MatrixEvent, Room } from 'matrix-js-sdk/src'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import Spinner from '../elements/Spinner'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import RoomAvatar from '../avatars/RoomAvatar'; +import BaseAvatar from '../avatars/BaseAvatar'; interface IProps { initialAvatarUrl?: string; @@ -155,7 +156,6 @@ export default class ChangeAvatar extends React.Component<IProps, IState> { // Having just set an avatar we just display that since it will take a little // time to propagate through to the RoomAvatar. if (this.props.room && !this.avatarSet) { - const RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); avatarImg = <RoomAvatar room={this.props.room} width={this.props.width} @@ -163,7 +163,6 @@ export default class ChangeAvatar extends React.Component<IProps, IState> { resizeMethod='crop' />; } else { - const BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); // XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ? avatarImg = <BaseAvatar width={this.props.width} diff --git a/src/components/views/settings/ChangeDisplayName.tsx b/src/components/views/settings/ChangeDisplayName.tsx index 09c9ecc23e..5b9703a46b 100644 --- a/src/components/views/settings/ChangeDisplayName.tsx +++ b/src/components/views/settings/ChangeDisplayName.tsx @@ -17,10 +17,10 @@ limitations under the License. */ import React from 'react'; -import * as sdk from '../../../index'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import EditableTextContainer from "../elements/EditableTextContainer"; @replaceableComponent("views.settings.ChangeDisplayName") export default class ChangeDisplayName extends React.Component { @@ -42,7 +42,6 @@ export default class ChangeDisplayName extends React.Component { }; public render(): JSX.Element { - const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer'); return ( <EditableTextContainer getInitialValue={this.getDisplayName} diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx index 6fd7f90c8e..b10820d6a5 100644 --- a/src/components/views/settings/DevicesPanel.tsx +++ b/src/components/views/settings/DevicesPanel.tsx @@ -20,12 +20,12 @@ import React from 'react'; import classNames from 'classnames'; import { IMyDevice } from "matrix-js-sdk/src/client"; -import * as sdk from '../../../index'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import InteractiveAuthDialog from "../dialogs/InteractiveAuthDialog"; interface IProps { className?: string; @@ -119,7 +119,6 @@ export default class DevicesPanel extends React.Component<IProps, IState> { } // pop up an interactive auth dialog - const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog"); const numDevices = this.state.selectedDevices.length; const dialogAesthetics = { diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx index d44147f591..3762f7be83 100644 --- a/src/components/views/settings/DevicesPanelEntry.tsx +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -18,12 +18,12 @@ limitations under the License. import React from 'react'; import { IMyDevice } from 'matrix-js-sdk/src'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { formatDate } from '../../../DateUtils'; import StyledCheckbox from '../elements/StyledCheckbox'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import EditableTextContainer from "../elements/EditableTextContainer"; interface IProps { device?: IMyDevice; @@ -52,8 +52,6 @@ export default class DevicesPanelEntry extends React.Component<IProps> { }; public render(): JSX.Element { - const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer'); - const device = this.props.device; let lastSeen = ""; diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx index f43fb55004..7b221aceec 100644 --- a/src/components/views/settings/IntegrationManager.tsx +++ b/src/components/views/settings/IntegrationManager.tsx @@ -17,12 +17,12 @@ limitations under the License. */ import React from 'react'; -import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher/dispatcher'; import { Key } from "../../../Keyboard"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { ActionPayload } from '../../../dispatcher/payloads'; +import Spinner from "../elements/Spinner"; interface IProps { // false to display an error saying that we couldn't connect to the integration manager @@ -85,7 +85,6 @@ export default class IntegrationManager extends React.Component<IProps, IState> public render(): JSX.Element { if (this.props.loading) { - const Spinner = sdk.getComponent("elements.Spinner"); return ( <div className='mx_IntegrationManager_loading'> <h3>{ _t("Connecting to integration manager...") }</h3> diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx index 888ff8967b..9bd7179f08 100644 --- a/src/components/views/settings/ProfileSettings.tsx +++ b/src/components/views/settings/ProfileSettings.tsx @@ -19,12 +19,13 @@ import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import Field from "../elements/Field"; import { getHostingLink } from '../../../utils/HostingLink'; -import * as sdk from "../../../index"; import { OwnProfileStore } from "../../../stores/OwnProfileStore"; import Modal from "../../../Modal"; import ErrorDialog from "../dialogs/ErrorDialog"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import AccessibleButton from '../elements/AccessibleButton'; +import AvatarSetting from './AvatarSetting'; interface IProps { @@ -174,8 +175,6 @@ export default class ProfileSettings extends React.Component<IProps, IState> { </span>; } - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - const AvatarSetting = sdk.getComponent('settings.AvatarSetting'); return ( <form onSubmit={this.saveProfile} From da127ecb765c2f52b06d6bad96beb1df390efc61 Mon Sep 17 00:00:00 2001 From: Germain <germain@souquet.com> Date: Wed, 25 Aug 2021 08:56:21 +0100 Subject: [PATCH 17/23] Relative imports from the js-sdk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Šimon Brandner <simon.bra.ag@gmail.com> --- src/components/views/rooms/ReadReceiptMarker.tsx | 2 +- src/components/views/settings/ChangeAvatar.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx index 28e1ec85e9..c1bd533d39 100644 --- a/src/components/views/rooms/ReadReceiptMarker.tsx +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -16,7 +16,7 @@ limitations under the License. */ import React, { createRef } from 'react'; -import { RoomMember } from 'matrix-js-sdk/src'; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { _t } from '../../../languageHandler'; import { formatDate } from '../../../DateUtils'; diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx index 7126fe8cc3..e629b9df08 100644 --- a/src/components/views/settings/ChangeAvatar.tsx +++ b/src/components/views/settings/ChangeAvatar.tsx @@ -73,7 +73,7 @@ export default class ChangeAvatar extends React.Component<IProps, IState> { // TODO: [REACT-WARNING] Replace with appropriate lifecycle event // eslint-disable-next-line - public UNSAFE_componentWillReceiveProps(newProps): void { + public UNSAFE_componentWillReceiveProps(newProps: IProps): void { if (this.avatarSet) { // don't clobber what the user has just set return; From 6945e3f103c017be3824b5a12e9dfe00046ac28a Mon Sep 17 00:00:00 2001 From: Germain Souquet <germains@element.io> Date: Wed, 25 Aug 2021 09:05:07 +0100 Subject: [PATCH 18/23] Fix ProfileSettings types --- src/components/views/elements/AccessibleButton.tsx | 4 ++-- src/components/views/settings/ProfileSettings.tsx | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 0ce9a3a030..75b6890112 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -19,7 +19,7 @@ import React, { ReactHTML } from 'react'; import { Key } from '../../../Keyboard'; import classnames from 'classnames'; -export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element>; +export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element> | React.FormEvent<Element>; /** * children: React's magic prop. Represents all children given to the element. @@ -39,7 +39,7 @@ interface IProps extends React.InputHTMLAttributes<Element> { tabIndex?: number; disabled?: boolean; className?: string; - onClick(e?: ButtonEvent): void; + onClick(e?: ButtonEvent): void | Promise<void>; } interface IAccessibleButtonProps extends React.InputHTMLAttributes<Element> { diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx index 9bd7179f08..e6e68299cb 100644 --- a/src/components/views/settings/ProfileSettings.tsx +++ b/src/components/views/settings/ProfileSettings.tsx @@ -27,10 +27,6 @@ import { mediaFromMxc } from "../../../customisations/Media"; import AccessibleButton from '../elements/AccessibleButton'; import AvatarSetting from './AvatarSetting'; -interface IProps { - -} - interface IState { userId?: string; originalDisplayName?: string; @@ -42,9 +38,9 @@ interface IState { } @replaceableComponent("views.settings.ProfileSettings") -export default class ProfileSettings extends React.Component<IProps, IState> { +export default class ProfileSettings extends React.Component<{}, IState> { private avatarUpload: React.RefObject<HTMLInputElement> = createRef(); - constructor(props: IProps) { + constructor(props: {}) { super(props); const client = MatrixClientPeg.get(); @@ -205,7 +201,7 @@ export default class ProfileSettings extends React.Component<IProps, IState> { </p> </div> <AvatarSetting - avatarUrl={this.state.avatarUrl} + avatarUrl={this.state.avatarUrl.toString()} avatarName={this.state.displayName || this.state.userId} avatarAltText={_t("Profile picture")} uploadAvatar={this.uploadAvatar} From 450140befd77258d3190fa0a0a6999f05739a553 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germains@element.io> Date: Wed, 25 Aug 2021 09:34:16 +0100 Subject: [PATCH 19/23] Fix linting issues --- .eslintrc.js | 5 +++++ src/components/structures/RoomDirectory.tsx | 2 +- src/components/views/avatars/MemberAvatar.tsx | 1 + src/components/views/right_panel/UserInfo.tsx | 2 +- src/components/views/rooms/ReadReceiptMarker.tsx | 8 ++++---- src/components/views/rooms/RoomDetailList.tsx | 2 +- src/components/views/rooms/RoomHeader.tsx | 2 +- src/components/views/settings/DevicesPanel.tsx | 10 ++++------ 8 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 827b373949..9d68942228 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -63,6 +63,11 @@ module.exports = { "@typescript-eslint/ban-ts-comment": "off", }, }], + settings: { + react: { + version: "detect", + } + } }; function buildRestrictedPropertiesOptions(properties, message) { diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx index 8d8609d1cf..3c5f99cc7d 100644 --- a/src/components/structures/RoomDirectory.tsx +++ b/src/components/structures/RoomDirectory.tsx @@ -347,7 +347,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> { }); } - private onRoomClicked = (room: IPublicRoomsChunkRoom, ev: ButtonEvent) => { + private onRoomClicked = (room: IPublicRoomsChunkRoom, ev: React.MouseEvent) => { // If room was shift-clicked, remove it from the room directory if (ev.shiftKey && !this.state.selectedCommunityId) { ev.preventDefault(); diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 11c24a5981..3c734705b7 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -36,6 +36,7 @@ interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | // Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser` viewUserOnClick?: boolean; title?: string; + style?: any; } interface IState { diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 138f5bf9fe..d15f349d62 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -428,7 +428,7 @@ const UserOptionsSection: React.FC<{ let directMessageButton; if (!isMe) { directMessageButton = ( - <AccessibleButton onClick={() => openDMForUser(cli, member.userId)} className="mx_UserInfo_field"> + <AccessibleButton onClick={() => { openDMForUser(cli, member.userId); }} className="mx_UserInfo_field"> { _t('Direct message') } </AccessibleButton> ); diff --git a/src/components/views/rooms/ReadReceiptMarker.tsx b/src/components/views/rooms/ReadReceiptMarker.tsx index c1bd533d39..cfc535b23d 100644 --- a/src/components/views/rooms/ReadReceiptMarker.tsx +++ b/src/components/views/rooms/ReadReceiptMarker.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from 'react'; +import React, { createRef, RefObject } from 'react'; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { _t } from '../../../languageHandler'; @@ -75,7 +75,7 @@ interface IReadReceiptMarkerStyle { @replaceableComponent("views.rooms.ReadReceiptMarker") export default class ReadReceiptMarker extends React.PureComponent<IProps, IState> { - private avatar: React.RefObject<HTMLDivElement> = createRef(); + private avatar: React.RefObject<HTMLDivElement | HTMLImageElement | HTMLSpanElement> = createRef(); static defaultProps = { leftOffset: 0, @@ -171,7 +171,7 @@ export default class ReadReceiptMarker extends React.PureComponent<IProps, IStat public render(): JSX.Element { if (this.state.suppressDisplay) { - return <div ref={this.avatar} />; + return <div ref={this.avatar as RefObject<HTMLDivElement>} />; } const style = { @@ -210,7 +210,7 @@ export default class ReadReceiptMarker extends React.PureComponent<IProps, IStat style={style} title={title} onClick={this.props.onClick} - inputRef={this.avatar} + inputRef={this.avatar as RefObject<HTMLImageElement>} /> </NodeAnimator> ); diff --git a/src/components/views/rooms/RoomDetailList.tsx b/src/components/views/rooms/RoomDetailList.tsx index cace94ce08..ed2a1fcb44 100644 --- a/src/components/views/rooms/RoomDetailList.tsx +++ b/src/components/views/rooms/RoomDetailList.tsx @@ -31,7 +31,7 @@ interface IProps { @replaceableComponent("views.rooms.RoomDetailList") export default class RoomDetailList extends React.Component<IProps> { public getRows(): JSX.Element[] { - if (!this.props.rooms) return []; s; + if (!this.props.rooms) return []; return this.props.rooms.map((room, index) => { return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />; }); diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index 15b25ed64b..d0e438bcda 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -195,7 +195,7 @@ export default class RoomHeader extends React.Component<IProps> { videoCallButton = <AccessibleTooltipButton className="mx_RoomHeader_button mx_RoomHeader_videoCallButton" - onClick={(ev) => ev.shiftKey ? + onClick={(ev: React.MouseEvent<Element>) => ev.shiftKey ? this.displayInfoDialogAboutScreensharing() : this.props.onCallPlaced(PlaceCallType.Video)} title={_t("Video call")} />; } diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx index b10820d6a5..4b1fb280a7 100644 --- a/src/components/views/settings/DevicesPanel.tsx +++ b/src/components/views/settings/DevicesPanel.tsx @@ -26,6 +26,9 @@ import Modal from '../../../Modal'; import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import InteractiveAuthDialog from "../dialogs/InteractiveAuthDialog"; +import DevicesPanelEntry from "./DevicesPanelEntry"; +import Spinner from "../elements/Spinner"; +import AccessibleButton from "../elements/AccessibleButton"; interface IProps { className?: string; @@ -175,7 +178,6 @@ export default class DevicesPanel extends React.Component<IProps, IState> { } private renderDevice = (device: IMyDevice): JSX.Element => { - const DevicesPanelEntry = sdk.getComponent('settings.DevicesPanelEntry'); return <DevicesPanelEntry key={device.device_id} device={device} @@ -185,9 +187,6 @@ export default class DevicesPanel extends React.Component<IProps, IState> { }; public render(): JSX.Element { - const Spinner = sdk.getComponent("elements.Spinner"); - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - if (this.state.deviceLoadError !== undefined) { const classes = classNames(this.props.className, "error"); return ( @@ -200,8 +199,7 @@ export default class DevicesPanel extends React.Component<IProps, IState> { const devices = this.state.devices; if (devices === undefined) { // still loading - const classes = this.props.className; - return <Spinner className={classes} />; + return <Spinner />; } devices.sort(this.deviceCompare); From 7938961d2731d88d4e56ce149ed3bd32d321ee78 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germains@element.io> Date: Wed, 25 Aug 2021 09:48:29 +0100 Subject: [PATCH 20/23] Update js-sdk imports to target individual files --- src/components/views/rooms/RoomUpgradeWarningBar.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.tsx b/src/components/views/rooms/RoomUpgradeWarningBar.tsx index 3380bd5392..4c1216b620 100644 --- a/src/components/views/rooms/RoomUpgradeWarningBar.tsx +++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx @@ -15,7 +15,10 @@ limitations under the License. */ import React from 'react'; -import { MatrixEvent, Room, RoomState } from 'matrix-js-sdk/src'; +import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { Room } from 'matrix-js-sdk/src/models/room'; +import { RoomState } from 'matrix-js-sdk/src/models/room-state'; + import Modal from '../../../Modal'; import { _t } from '../../../languageHandler'; From e1e0278190faf78b9dcca8ccd5b88441dd307380 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germains@element.io> Date: Wed, 25 Aug 2021 10:04:54 +0100 Subject: [PATCH 21/23] Update js-sdk imports to target individual files --- src/components/views/settings/ChangeAvatar.tsx | 3 ++- src/components/views/settings/DevicesPanelEntry.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx index e629b9df08..08ecd3d065 100644 --- a/src/components/views/settings/ChangeAvatar.tsx +++ b/src/components/views/settings/ChangeAvatar.tsx @@ -16,7 +16,8 @@ limitations under the License. */ import React from 'react'; -import { MatrixEvent, Room } from 'matrix-js-sdk/src'; +import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { Room } from 'matrix-js-sdk/src/models/room'; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { _t } from '../../../languageHandler'; import Spinner from '../elements/Spinner'; diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx index 3762f7be83..940ccf8ba1 100644 --- a/src/components/views/settings/DevicesPanelEntry.tsx +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -16,7 +16,7 @@ limitations under the License. */ import React from 'react'; -import { IMyDevice } from 'matrix-js-sdk/src'; +import { IMyDevice } from 'matrix-js-sdk/src/client'; import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; From cb8e62c0b2547b2c1f9745210c1efe27f67d370f Mon Sep 17 00:00:00 2001 From: Germain Souquet <germains@element.io> Date: Thu, 26 Aug 2021 08:02:36 +0100 Subject: [PATCH 22/23] human linter and copyright fixer --- src/components/views/rooms/RoomDetailList.tsx | 4 ++-- src/components/views/rooms/RoomUpgradeWarningBar.tsx | 2 +- src/components/views/rooms/SimpleRoomHeader.tsx | 3 +-- src/components/views/rooms/TopUnreadMessagesBar.tsx | 3 +-- src/components/views/settings/ChangeAvatar.tsx | 7 +++---- src/components/views/settings/ChangeDisplayName.tsx | 3 +-- src/components/views/settings/DevicesPanel.tsx | 3 +-- src/components/views/settings/DevicesPanelEntry.tsx | 2 +- src/components/views/settings/IntegrationManager.tsx | 3 +-- src/components/views/settings/ProfileSettings.tsx | 2 ++ 10 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/components/views/rooms/RoomDetailList.tsx b/src/components/views/rooms/RoomDetailList.tsx index ed2a1fcb44..869ab9e8f3 100644 --- a/src/components/views/rooms/RoomDetailList.tsx +++ b/src/components/views/rooms/RoomDetailList.tsx @@ -30,14 +30,14 @@ interface IProps { @replaceableComponent("views.rooms.RoomDetailList") export default class RoomDetailList extends React.Component<IProps> { - public getRows(): JSX.Element[] { + private getRows(): JSX.Element[] { if (!this.props.rooms) return []; return this.props.rooms.map((room, index) => { return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />; }); } - public onDetailsClick = (ev: React.MouseEvent, room: Room): void => { + private onDetailsClick = (ev: React.MouseEvent, room: Room): void => { dis.dispatch({ action: 'view_room', room_id: room.roomId, diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.tsx b/src/components/views/rooms/RoomUpgradeWarningBar.tsx index 4c1216b620..eb334ab825 100644 --- a/src/components/views/rooms/RoomUpgradeWarningBar.tsx +++ b/src/components/views/rooms/RoomUpgradeWarningBar.tsx @@ -1,5 +1,5 @@ /* -Copyright 2018-2021 New Vector Ltd +Copyright 2018-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/rooms/SimpleRoomHeader.tsx b/src/components/views/rooms/SimpleRoomHeader.tsx index b81e906559..d6effaceb4 100644 --- a/src/components/views/rooms/SimpleRoomHeader.tsx +++ b/src/components/views/rooms/SimpleRoomHeader.tsx @@ -1,6 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd -Copyright 2021 New Vector Ltd +Copyright 2016-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/rooms/TopUnreadMessagesBar.tsx b/src/components/views/rooms/TopUnreadMessagesBar.tsx index 01797299cf..ec2472f966 100644 --- a/src/components/views/rooms/TopUnreadMessagesBar.tsx +++ b/src/components/views/rooms/TopUnreadMessagesBar.tsx @@ -1,7 +1,6 @@ /* -Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd -Copyright 2019-2021 New Vector Ltd +Copyright 2016-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/settings/ChangeAvatar.tsx b/src/components/views/settings/ChangeAvatar.tsx index 08ecd3d065..36178540f7 100644 --- a/src/components/views/settings/ChangeAvatar.tsx +++ b/src/components/views/settings/ChangeAvatar.tsx @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2021 New Vector Ltd +Copyright 2015-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -90,7 +89,7 @@ export default class ChangeAvatar extends React.Component<IProps, IState> { } } - public onRoomStateEvents = (ev: MatrixEvent) => { + private onRoomStateEvents = (ev: MatrixEvent) => { if (!this.props.room) { return; } @@ -106,7 +105,7 @@ export default class ChangeAvatar extends React.Component<IProps, IState> { } }; - public setAvatarFromFile(file): Promise<{}> { + private setAvatarFromFile(file: File): Promise<{}> { let newUrl = null; this.setState({ diff --git a/src/components/views/settings/ChangeDisplayName.tsx b/src/components/views/settings/ChangeDisplayName.tsx index 5b9703a46b..016f519dd9 100644 --- a/src/components/views/settings/ChangeDisplayName.tsx +++ b/src/components/views/settings/ChangeDisplayName.tsx @@ -1,7 +1,6 @@ /* -Copyright 2015, 2016 OpenMarket Ltd Copyright 2018 - 2021 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2015-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx index 4b1fb280a7..b6797b8ad5 100644 --- a/src/components/views/settings/DevicesPanel.tsx +++ b/src/components/views/settings/DevicesPanel.tsx @@ -1,7 +1,6 @@ /* -Copyright 2016 OpenMarket Ltd Copyright 2019 The Matrix.org Foundation C.I.C. -Copyright 2021 New Vector Ltd +Copyright 2016-2021 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. diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx index 940ccf8ba1..b589ffc7a1 100644 --- a/src/components/views/settings/DevicesPanelEntry.tsx +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -1,5 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd +Copyright 2016-2021 The Matrix.org Foundation C.I.C. Copyright 2021 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx index 7b221aceec..f9b3f67fad 100644 --- a/src/components/views/settings/IntegrationManager.tsx +++ b/src/components/views/settings/IntegrationManager.tsx @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2015-2021 The Matrix.org Foundation C.I.C. Copyright 2021 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx index e6e68299cb..9e1f0444b3 100644 --- a/src/components/views/settings/ProfileSettings.tsx +++ b/src/components/views/settings/ProfileSettings.tsx @@ -1,5 +1,6 @@ /* Copyright 2019-2021 New Vector Ltd +Copyright 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -40,6 +41,7 @@ interface IState { @replaceableComponent("views.settings.ProfileSettings") export default class ProfileSettings extends React.Component<{}, IState> { private avatarUpload: React.RefObject<HTMLInputElement> = createRef(); + constructor(props: {}) { super(props); From ae16695713eb47f0034d7b61b16ab9c8050ed334 Mon Sep 17 00:00:00 2001 From: Germain Souquet <germains@element.io> Date: Wed, 1 Sep 2021 10:19:25 +0100 Subject: [PATCH 23/23] Fix Apache copyright headers --- src/components/views/rooms/TopUnreadMessagesBar.tsx | 3 +-- src/components/views/settings/ChangeDisplayName.tsx | 3 +-- src/components/views/settings/DevicesPanel.tsx | 3 +-- src/components/views/settings/DevicesPanelEntry.tsx | 3 +-- src/components/views/settings/IntegrationManager.tsx | 3 +-- src/components/views/settings/ProfileSettings.tsx | 3 +-- 6 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/components/views/rooms/TopUnreadMessagesBar.tsx b/src/components/views/rooms/TopUnreadMessagesBar.tsx index ec2472f966..14f9a27f2d 100644 --- a/src/components/views/rooms/TopUnreadMessagesBar.tsx +++ b/src/components/views/rooms/TopUnreadMessagesBar.tsx @@ -1,6 +1,5 @@ /* -Copyright 2017 Vector Creations Ltd -Copyright 2016-2021 The Matrix.org Foundation C.I.C. +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/settings/ChangeDisplayName.tsx b/src/components/views/settings/ChangeDisplayName.tsx index 016f519dd9..9f0f813ec6 100644 --- a/src/components/views/settings/ChangeDisplayName.tsx +++ b/src/components/views/settings/ChangeDisplayName.tsx @@ -1,6 +1,5 @@ /* -Copyright 2018 - 2021 New Vector Ltd -Copyright 2015-2021 The Matrix.org Foundation C.I.C. +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/settings/DevicesPanel.tsx b/src/components/views/settings/DevicesPanel.tsx index b6797b8ad5..5e297bbea6 100644 --- a/src/components/views/settings/DevicesPanel.tsx +++ b/src/components/views/settings/DevicesPanel.tsx @@ -1,6 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. -Copyright 2016-2021 New Vector Ltd +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx index b589ffc7a1..d033bc41a9 100644 --- a/src/components/views/settings/DevicesPanelEntry.tsx +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -1,6 +1,5 @@ /* -Copyright 2016-2021 The Matrix.org Foundation C.I.C. -Copyright 2021 New Vector Ltd +Copyright 2016 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx index f9b3f67fad..0b880c019f 100644 --- a/src/components/views/settings/IntegrationManager.tsx +++ b/src/components/views/settings/IntegrationManager.tsx @@ -1,6 +1,5 @@ /* -Copyright 2015-2021 The Matrix.org Foundation C.I.C. -Copyright 2021 New Vector Ltd +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/settings/ProfileSettings.tsx b/src/components/views/settings/ProfileSettings.tsx index 9e1f0444b3..9563280550 100644 --- a/src/components/views/settings/ProfileSettings.tsx +++ b/src/components/views/settings/ProfileSettings.tsx @@ -1,6 +1,5 @@ /* -Copyright 2019-2021 New Vector Ltd -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.