From 4c30ecd990eb950b781d485289359404458580e6 Mon Sep 17 00:00:00 2001 From: Germain Date: Mon, 31 Oct 2022 17:23:49 +0000 Subject: [PATCH 1/5] Always use current profile on thread events --- src/components/views/avatars/MemberAvatar.tsx | 142 ++++++++---------- .../views/messages/SenderProfile.tsx | 57 +++---- src/components/views/rooms/EventTile.tsx | 7 - 3 files changed, 87 insertions(+), 119 deletions(-) diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 48664394731..237a9f01b11 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials'; import { logger } from "matrix-js-sdk/src/logger"; @@ -27,7 +27,8 @@ import { mediaFromMxc } from "../../../customisations/Media"; import { CardContext } from '../right_panel/context'; import UserIdentifierCustomisations from '../../../customisations/UserIdentifier'; import SettingsStore from "../../../settings/SettingsStore"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import MatrixClientContext from '../../../contexts/MatrixClientContext'; +import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext'; interface IProps extends Omit, "name" | "idName" | "url"> { member: RoomMember | null; @@ -46,100 +47,85 @@ interface IProps extends Omit, "name" | hideTitle?: boolean; } -interface IState { - name: string; - title: string; - imageUrl?: string; -} - -export default class MemberAvatar extends React.PureComponent { - public static defaultProps = { - width: 40, - height: 40, - resizeMethod: 'crop', - viewUserOnClick: false, - }; - - constructor(props: IProps) { - super(props); - - this.state = MemberAvatar.getState(props); - } +export default function MemberAvatar({ + width, + height, + resizeMethod = 'crop', + viewUserOnClick, + ...props +}: IProps) { + const cli = useContext(MatrixClientContext); + const card = useContext(CardContext); + const roomContext = useContext(RoomContext); + + const [name, setName] = useState(); + const [title, setTitle] = useState(); + const [imageUrl, setImageUrl] = useState(); + const [userId, setUserId] = useState(); + + useEffect(() => { + let member = props.member; - public static getDerivedStateFromProps(nextProps: IProps): IState { - return MemberAvatar.getState(nextProps); - } + const useOnlyCurrentProfiles = (member && !props.forceHistorical + && SettingsStore.getValue("useOnlyCurrentProfiles")) + || roomContext?.timelineRenderingType === TimelineRenderingType.ThreadsList + || roomContext?.timelineRenderingType === TimelineRenderingType.Thread; - private static getState(props: IProps): IState { - let member = props.member; - if (member && !props.forceHistorical && SettingsStore.getValue("useOnlyCurrentProfiles")) { - const room = MatrixClientPeg.get().getRoom(member.roomId); + if (useOnlyCurrentProfiles) { + const room = cli.getRoom(member.roomId); if (room) { member = room.getMember(member.userId); } } if (member?.name) { - let imageUrl = null; const userTitle = UserIdentifierCustomisations.getDisplayUserIdentifier( member.userId, { roomId: member?.roomId }, ); if (member.getMxcAvatarUrl()) { - imageUrl = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp( - props.width, - props.height, - props.resizeMethod, - ); + setImageUrl(mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp( + width, + height, + resizeMethod, + )); } - return { - name: member.name, - title: props.title || userTitle, - imageUrl: imageUrl, - }; + setName(member.name); + setTitle(props.title || userTitle); } else if (props.fallbackUserId) { - return { - name: props.fallbackUserId, - title: props.fallbackUserId, - }; + setName(props.fallbackUserId); + setTitle(props.fallbackUserId); } else { logger.error("MemberAvatar called somehow with null member or fallbackUserId"); - return {} as IState; // prevent an explosion } - } - - render() { - let { - member, - fallbackUserId, - onClick, - viewUserOnClick, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - forceHistorical, - hideTitle, - ...otherProps - } = this.props; - const userId = member ? member.userId : fallbackUserId; - if (viewUserOnClick) { - onClick = () => { + setUserId(member?.userId ?? props.fallbackUserId); + }, [cli, + height, + props.fallbackUserId, + props.forceHistorical, + props.member, + props.title, + resizeMethod, + roomContext?.timelineRenderingType, + width, + ]); + + return ( + { dis.dispatch({ action: Action.ViewUser, - member: this.props.member, - push: this.context.isCard, + member: props.member, + push: card.isCard, }); - }; - } - - return ( - - ); - } + } : props.onClick} + /> + ); } - -MemberAvatar.contextType = CardContext; diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index db44cfeb04d..3d397330253 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -14,7 +14,7 @@ limitations under the License. */ -import React from 'react'; +import React, { useContext } from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MsgType } from "matrix-js-sdk/src/@types/event"; @@ -22,47 +22,36 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import DisambiguatedProfile from "./DisambiguatedProfile"; import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext'; import SettingsStore from "../../../settings/SettingsStore"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; interface IProps { mxEvent: MatrixEvent; onClick?(): void; } -export default class SenderProfile extends React.PureComponent { - public static contextType = MatrixClientContext; - public context!: React.ContextType; +export default function SenderProfile({ mxEvent, onClick }: IProps) { + const roomContext = useContext(RoomContext); + const cli = useContext(MatrixClientContext); - render() { - const { mxEvent, onClick } = this.props; - const msgtype = mxEvent.getContent().msgtype; + if (mxEvent.getContent().msgtype === MsgType.Emote) { + return null; + } - let member = mxEvent.sender; - if (SettingsStore.getValue("useOnlyCurrentProfiles")) { - const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); - if (room) { - member = room.getMember(mxEvent.getSender()); - } + let member = mxEvent.sender; + if (SettingsStore.getValue("useOnlyCurrentProfiles") + || roomContext.timelineRenderingType === TimelineRenderingType.ThreadsList + || roomContext.timelineRenderingType === TimelineRenderingType.Thread + ) { + const room = cli.getRoom(mxEvent.getRoomId()); + if (room) { + member = room.getMember(mxEvent.getSender()); } - - return - { roomContext => { - if (msgtype === MsgType.Emote && - roomContext.timelineRenderingType !== TimelineRenderingType.ThreadsList - ) { - return null; // emote message must include the name so don't duplicate it - } - - return ( - - ); - } } - ; } + + return ; } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index f6b249b2913..4a3b1ebf8d6 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1311,7 +1311,6 @@ export class UnwrappedEventTile extends React.Component ]); } case TimelineRenderingType.Thread: { - const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); return React.createElement(this.props.as || "li", { "ref": this.ref, "className": classes, @@ -1325,12 +1324,6 @@ export class UnwrappedEventTile extends React.Component "onMouseEnter": () => this.setState({ hover: true }), "onMouseLeave": () => this.setState({ hover: false }), }, [ - ,
{ avatar } { sender } From 578b8b0a4ed33e9ceb2bd67ad12311978d457f28 Mon Sep 17 00:00:00 2001 From: Germain Date: Mon, 31 Oct 2022 17:52:16 +0000 Subject: [PATCH 2/5] Fix snapshot tests --- .../__snapshots__/BeaconMarker-test.tsx.snap | 48 ++++++++++++++++++- .../__snapshots__/TextualBody-test.tsx.snap | 2 +- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/test/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap b/test/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap index 5bb297fc403..460b0acd14f 100644 --- a/test/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap +++ b/test/components/views/beacon/__snapshots__/BeaconMarker-test.tsx.snap @@ -184,21 +184,65 @@ exports[` renders marker when beacon has location 1`] = ` Symbol(kCapture): false, } } - resizeMethod="crop" viewUserOnClick={false} width={36} > renders formatted m.text correctly pills do not appear " `; -exports[` renders formatted m.text correctly pills get injected correctly into the DOM 1`] = `"Hey \\"\\"Member"`; +exports[` renders formatted m.text correctly pills get injected correctly into the DOM 1`] = `"Hey \\"\\"Member"`; From 5484e2b4d8a0858c07384f279c44f0651b56db5e Mon Sep 17 00:00:00 2001 From: Germain Date: Wed, 2 Nov 2022 11:02:20 +0000 Subject: [PATCH 3/5] create useRoomMemberProfile hook --- src/components/views/avatars/MemberAvatar.tsx | 76 ++++++------------- .../views/messages/SenderProfile.tsx | 45 ++++------- src/hooks/room/useRoomMemberProfile.ts | 46 +++++++++++ 3 files changed, 85 insertions(+), 82 deletions(-) create mode 100644 src/hooks/room/useRoomMemberProfile.ts diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 237a9f01b11..8d72a7de353 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -15,10 +15,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext } from 'react'; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials'; -import { logger } from "matrix-js-sdk/src/logger"; import dis from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; @@ -26,9 +25,7 @@ import BaseAvatar from "./BaseAvatar"; import { mediaFromMxc } from "../../../customisations/Media"; import { CardContext } from '../right_panel/context'; import UserIdentifierCustomisations from '../../../customisations/UserIdentifier'; -import SettingsStore from "../../../settings/SettingsStore"; -import MatrixClientContext from '../../../contexts/MatrixClientContext'; -import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext'; +import { useRoomMemberProfile } from '../../../hooks/room/useRoomMemberProfile'; interface IProps extends Omit, "name" | "idName" | "url"> { member: RoomMember | null; @@ -54,60 +51,33 @@ export default function MemberAvatar({ viewUserOnClick, ...props }: IProps) { - const cli = useContext(MatrixClientContext); const card = useContext(CardContext); - const roomContext = useContext(RoomContext); - const [name, setName] = useState(); - const [title, setTitle] = useState(); - const [imageUrl, setImageUrl] = useState(); - const [userId, setUserId] = useState(); + const member = useRoomMemberProfile({ + userId: props.member?.userId, + member: props.member, + forceHistorical: props.forceHistorical, + }); - useEffect(() => { - let member = props.member; - - const useOnlyCurrentProfiles = (member && !props.forceHistorical - && SettingsStore.getValue("useOnlyCurrentProfiles")) - || roomContext?.timelineRenderingType === TimelineRenderingType.ThreadsList - || roomContext?.timelineRenderingType === TimelineRenderingType.Thread; - - if (useOnlyCurrentProfiles) { - const room = cli.getRoom(member.roomId); - if (room) { - member = room.getMember(member.userId); - } - } - if (member?.name) { - const userTitle = UserIdentifierCustomisations.getDisplayUserIdentifier( - member.userId, { roomId: member?.roomId }, + const name = member?.name ?? props.fallbackUserId; + let title: string = props.title; + let imageUrl: string | undefined; + if (member?.name) { + if (member.getMxcAvatarUrl()) { + imageUrl = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp( + width, + height, + resizeMethod, ); - if (member.getMxcAvatarUrl()) { - setImageUrl(mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp( - width, - height, - resizeMethod, - )); - } - setName(member.name); - setTitle(props.title || userTitle); - } else if (props.fallbackUserId) { - setName(props.fallbackUserId); - setTitle(props.fallbackUserId); - } else { - logger.error("MemberAvatar called somehow with null member or fallbackUserId"); } - setUserId(member?.userId ?? props.fallbackUserId); - }, [cli, - height, - props.fallbackUserId, - props.forceHistorical, - props.member, - props.title, - resizeMethod, - roomContext?.timelineRenderingType, - width, - ]); + if (!title) { + title = UserIdentifierCustomisations.getDisplayUserIdentifier( + member.userId, { roomId: member?.roomId }, + ) ?? props.fallbackUserId; + } + } + const userId = member?.userId ?? props.fallbackUserId; return ( ; + const member = useRoomMemberProfile({ + userId: mxEvent.getSender(), + member: mxEvent.sender, + }); + + return mxEvent.getContent().msgtype !== MsgType.Emote + ? + : null; } diff --git a/src/hooks/room/useRoomMemberProfile.ts b/src/hooks/room/useRoomMemberProfile.ts new file mode 100644 index 00000000000..45ec0a12984 --- /dev/null +++ b/src/hooks/room/useRoomMemberProfile.ts @@ -0,0 +1,46 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { useContext, useEffect, useState } from "react"; + +import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext"; +import { useSettingValue } from "../useSettings"; + +export function useRoomMemberProfile({ + userId = "", + member: propMember, + forceHistorical = false, +}: { + userId: string | undefined; + member?: RoomMember; + forceHistorical?: boolean; +}): RoomMember { + const [member, setMember] = useState(propMember); + + const context = useContext(RoomContext); + const useOnlyCurrentProfiles = useSettingValue("useOnlyCurrentProfiles"); + + useEffect(() => { + const threadContexts = [TimelineRenderingType.ThreadsList, TimelineRenderingType.Thread]; + if ((propMember && !forceHistorical && useOnlyCurrentProfiles) + || threadContexts.includes(context?.timelineRenderingType)) { + setMember(context.room.getMember(userId)); + } + }, [forceHistorical, propMember, context.room, context?.timelineRenderingType, useOnlyCurrentProfiles, userId]); + + return member; +} From 21c4c57df707bc7a5c12bf76d16c463b14ebac4c Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 8 Nov 2022 10:32:59 +0000 Subject: [PATCH 4/5] Strict types --- src/components/views/avatars/MemberAvatar.tsx | 6 +++--- src/hooks/room/useRoomMemberProfile.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 8d72a7de353..d849069f0ba 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -60,11 +60,11 @@ export default function MemberAvatar({ }); const name = member?.name ?? props.fallbackUserId; - let title: string = props.title; + let title: string | undefined = props.title; let imageUrl: string | undefined; if (member?.name) { if (member.getMxcAvatarUrl()) { - imageUrl = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp( + imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "").getThumbnailOfSourceHttp( width, height, resizeMethod, @@ -73,7 +73,7 @@ export default function MemberAvatar({ if (!title) { title = UserIdentifierCustomisations.getDisplayUserIdentifier( - member.userId, { roomId: member?.roomId }, + member?.userId ?? "", { roomId: member?.roomId ?? "" }, ) ?? props.fallbackUserId; } } diff --git a/src/hooks/room/useRoomMemberProfile.ts b/src/hooks/room/useRoomMemberProfile.ts index 45ec0a12984..8afab490505 100644 --- a/src/hooks/room/useRoomMemberProfile.ts +++ b/src/hooks/room/useRoomMemberProfile.ts @@ -26,10 +26,10 @@ export function useRoomMemberProfile({ forceHistorical = false, }: { userId: string | undefined; - member?: RoomMember; + member?: RoomMember | null; forceHistorical?: boolean; -}): RoomMember { - const [member, setMember] = useState(propMember); +}): RoomMember | undefined | null { + const [member, setMember] = useState(propMember); const context = useContext(RoomContext); const useOnlyCurrentProfiles = useSettingValue("useOnlyCurrentProfiles"); @@ -38,7 +38,7 @@ export function useRoomMemberProfile({ const threadContexts = [TimelineRenderingType.ThreadsList, TimelineRenderingType.Thread]; if ((propMember && !forceHistorical && useOnlyCurrentProfiles) || threadContexts.includes(context?.timelineRenderingType)) { - setMember(context.room.getMember(userId)); + setMember(context?.room?.getMember(userId)); } }, [forceHistorical, propMember, context.room, context?.timelineRenderingType, useOnlyCurrentProfiles, userId]); From ebe34a452a6f39101286e8ba5a08a7e651a7cbcd Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 8 Nov 2022 10:43:28 +0000 Subject: [PATCH 5/5] strict ts --- src/components/views/avatars/MemberAvatar.tsx | 2 +- src/components/views/messages/DisambiguatedProfile.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index d849069f0ba..c0f36c22665 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -85,7 +85,7 @@ export default function MemberAvatar({ width={width} height={height} resizeMethod={resizeMethod} - name={name} + name={name ?? ""} title={props.hideTitle ? undefined : title} idName={userId} url={imageUrl} diff --git a/src/components/views/messages/DisambiguatedProfile.tsx b/src/components/views/messages/DisambiguatedProfile.tsx index 36850e916ea..30053b3fbd8 100644 --- a/src/components/views/messages/DisambiguatedProfile.tsx +++ b/src/components/views/messages/DisambiguatedProfile.tsx @@ -23,7 +23,7 @@ import { getUserNameColorClass } from '../../../utils/FormattingUtils'; import UserIdentifier from "../../../customisations/UserIdentifier"; interface IProps { - member?: RoomMember; + member?: RoomMember | null; fallbackName: string; onClick?(): void; colored?: boolean;