From 24f721e414768a3a85734f31dc39d411fc427d3c Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Wed, 18 May 2022 19:00:59 -0400 Subject: [PATCH] Display room avatars --- src/Avatar.tsx | 64 ++++++++++++++++++++++++++++++----- src/Facepile.jsx | 13 ++----- src/Header.jsx | 7 ++-- src/home/useGroupCallRooms.js | 2 +- src/profile/useProfile.js | 11 ++---- src/room/GroupCallView.jsx | 6 ++++ src/room/InCallView.jsx | 13 +++---- src/room/LobbyView.jsx | 3 +- src/room/PTTButton.tsx | 7 +--- src/room/PTTCallView.tsx | 18 +++++----- src/room/VideoPreview.jsx | 7 +--- src/room/useRoomAvatar.ts | 24 +++++++++++++ 12 files changed, 112 insertions(+), 63 deletions(-) create mode 100644 src/room/useRoomAvatar.ts diff --git a/src/Avatar.tsx b/src/Avatar.tsx index eda272a..9a9bae8 100644 --- a/src/Avatar.tsx +++ b/src/Avatar.tsx @@ -1,6 +1,9 @@ -import React, { useMemo } from "react"; +import React, { useMemo, CSSProperties } from "react"; import classNames from "classnames"; +import { MatrixClient } from "matrix-js-sdk/src/client"; +import { getAvatarUrl } from "./matrix-utils"; +import { useClient } from "./ClientContext"; import styles from "./Avatar.module.css"; const backgroundColors = [ @@ -14,6 +17,22 @@ const backgroundColors = [ "#74D12C", ]; +export enum Size { + XS = "xs", + SM = "sm", + MD = "md", + LG = "lg", + XL = "xl", +} + +export const sizes = new Map([ + [Size.XS, 22], + [Size.SM, 32], + [Size.MD, 36], + [Size.LG, 42], + [Size.XL, 90], +]); + function hashStringToArrIndex(str: string, arrLength: number) { let sum = 0; @@ -24,24 +43,51 @@ function hashStringToArrIndex(str: string, arrLength: number) { return sum % arrLength; } +const resolveAvatarSrc = (client: MatrixClient, src: string, size: number) => + src?.startsWith("mxc://") ? client && getAvatarUrl(client, src, size) : src; + interface Props extends React.HTMLAttributes { bgKey?: string; src: string; fallback: string; - size?: number; + size?: Size | number; className: string; - style: React.CSSProperties; + style?: CSSProperties; } export const Avatar: React.FC = ({ bgKey, src, fallback, - size, + size = Size.MD, className, - style, + style = {}, ...rest }) => { + const { client } = useClient(); + + const [sizeClass, sizePx, sizeStyle] = useMemo( + () => + Object.values(Size).includes(size as Size) + ? [styles[size as string], sizes.get(size as Size), {}] + : [ + null, + size as number, + { + width: size, + height: size, + borderRadius: size, + fontSize: Math.round((size as number) / 2), + }, + ], + [size] + ); + + const resolvedSrc = useMemo( + () => resolveAvatarSrc(client, src, sizePx), + [client, src, sizePx] + ); + const backgroundColor = useMemo(() => { const index = hashStringToArrIndex( bgKey || fallback || src || "", @@ -53,12 +99,12 @@ export const Avatar: React.FC = ({ /* eslint-disable jsx-a11y/alt-text */ return (
- {src ? ( - + {resolvedSrc ? ( + ) : typeof fallback === "string" ? ( {fallback} ) : ( diff --git a/src/Facepile.jsx b/src/Facepile.jsx index 830a63d..aace9b1 100644 --- a/src/Facepile.jsx +++ b/src/Facepile.jsx @@ -1,8 +1,7 @@ import React from "react"; import styles from "./Facepile.module.css"; import classNames from "classnames"; -import { Avatar } from "./Avatar"; -import { getAvatarUrl } from "./matrix-utils"; +import { Avatar, sizes } from "./Avatar"; const overlapMap = { xs: 2, @@ -10,12 +9,6 @@ const overlapMap = { md: 8, }; -const sizeMap = { - xs: 24, - sm: 32, - md: 36, -}; - export function Facepile({ className, client, @@ -24,7 +17,7 @@ export function Facepile({ size, ...rest }) { - const _size = sizeMap[size]; + const _size = sizes.get(size); const _overlap = overlapMap[size]; return ( @@ -40,7 +33,7 @@ export function Facepile({
@@ -73,13 +74,13 @@ export function RoomHeaderInfo({ roomName }) { ); } -export function RoomSetupHeaderInfo({ roomName, ...rest }) { +export function RoomSetupHeaderInfo({ roomName, avatarUrl, ...rest }) { const ref = useRef(); const { buttonProps } = useButton(rest, ref); return ( ); } diff --git a/src/home/useGroupCallRooms.js b/src/home/useGroupCallRooms.js index 8cd0958..4177a5f 100644 --- a/src/home/useGroupCallRooms.js +++ b/src/home/useGroupCallRooms.js @@ -79,7 +79,7 @@ export function useGroupCallRooms(client) { return { roomId: room.getCanonicalAlias() || room.roomId, roomName: room.name, - avatarUrl: null, + avatarUrl: room.getMxcAvatarUrl(), room, groupCall, participants: [...groupCall.participants], diff --git a/src/profile/useProfile.js b/src/profile/useProfile.js index 74b2870..2207dde 100644 --- a/src/profile/useProfile.js +++ b/src/profile/useProfile.js @@ -15,7 +15,6 @@ limitations under the License. */ import { useState, useCallback, useEffect } from "react"; -import { getAvatarUrl } from "../matrix-utils"; export function useProfile(client) { const [{ loading, displayName, avatarUrl, error, success }, setState] = @@ -26,7 +25,7 @@ export function useProfile(client) { success: false, loading: false, displayName: user?.rawDisplayName, - avatarUrl: user && client && getAvatarUrl(client, user.avatarUrl), + avatarUrl: user?.avatarUrl, error: null, }; }); @@ -37,7 +36,7 @@ export function useProfile(client) { success: false, loading: false, displayName, - avatarUrl: getAvatarUrl(client, avatarUrl), + avatarUrl, error: null, }); }; @@ -84,11 +83,7 @@ export function useProfile(client) { setState((prev) => ({ ...prev, displayName, - avatarUrl: removeAvatar - ? null - : mxcAvatarUrl - ? getAvatarUrl(client, mxcAvatarUrl) - : prev.avatarUrl, + avatarUrl: removeAvatar ? null : mxcAvatarUrl ?? prev.avatarUrl, loading: false, success: true, })); diff --git a/src/room/GroupCallView.jsx b/src/room/GroupCallView.jsx index 0eb6b38..dcd9da3 100644 --- a/src/room/GroupCallView.jsx +++ b/src/room/GroupCallView.jsx @@ -23,6 +23,7 @@ import { LobbyView } from "./LobbyView"; import { InCallView } from "./InCallView"; import { PTTCallView } from "./PTTCallView"; import { CallEndedView } from "./CallEndedView"; +import { useRoomAvatar } from "./useRoomAvatar"; import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler"; import { useLocationNavigation } from "../useLocationNavigation"; @@ -67,6 +68,8 @@ export function GroupCallView({ participants, } = useGroupCall(groupCall); + const avatarUrl = useRoomAvatar(groupCall.room); + useEffect(() => { window.groupCall = groupCall; }, [groupCall]); @@ -96,6 +99,7 @@ export function GroupCallView({ client={client} roomId={roomId} roomName={groupCall.room.name} + avatarUrl={avatarUrl} groupCall={groupCall} participants={participants} userMediaFeeds={userMediaFeeds} @@ -110,6 +114,7 @@ export function GroupCallView({ groupCall={groupCall} client={client} roomName={groupCall.room.name} + avatarUrl={avatarUrl} microphoneMuted={microphoneMuted} localVideoMuted={localVideoMuted} toggleLocalVideoMuted={toggleLocalVideoMuted} @@ -142,6 +147,7 @@ export function GroupCallView({ groupCall={groupCall} hasLocalParticipant={hasLocalParticipant} roomName={groupCall.room.name} + avatarUrl={avatarUrl} state={state} onInitLocalCallFeed={initLocalCallFeed} localCallFeed={localCallFeed} diff --git a/src/room/InCallView.jsx b/src/room/InCallView.jsx index 066e296..0cc1751 100644 --- a/src/room/InCallView.jsx +++ b/src/room/InCallView.jsx @@ -25,7 +25,6 @@ import { import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header"; import { VideoGrid, useVideoGridLayout } from "../video-grid/VideoGrid"; import { VideoTileContainer } from "../video-grid/VideoTileContainer"; -import { getAvatarUrl } from "../matrix-utils"; import { GroupCallInspector } from "./GroupCallInspector"; import { OverflowMenu } from "./OverflowMenu"; import { GridLayoutMenu } from "./GridLayoutMenu"; @@ -46,6 +45,7 @@ export function InCallView({ client, groupCall, roomName, + avatarUrl, microphoneMuted, localVideoMuted, toggleLocalVideoMuted, @@ -125,13 +125,8 @@ export function InCallView({ return ( @@ -149,7 +144,7 @@ export function InCallView({
- + diff --git a/src/room/LobbyView.jsx b/src/room/LobbyView.jsx index 7459e64..283735d 100644 --- a/src/room/LobbyView.jsx +++ b/src/room/LobbyView.jsx @@ -32,6 +32,7 @@ export function LobbyView({ client, groupCall, roomName, + avatarUrl, state, onInitLocalCallFeed, onEnter, @@ -72,7 +73,7 @@ export function LobbyView({
- + diff --git a/src/room/PTTButton.tsx b/src/room/PTTButton.tsx index a671186..4da324c 100644 --- a/src/room/PTTButton.tsx +++ b/src/room/PTTButton.tsx @@ -148,12 +148,7 @@ export const PTTButton: React.FC = ({ ) : ( = ({ client, roomId, roomName, + avatarUrl, groupCall, participants, userMediaFeeds, @@ -105,7 +106,6 @@ export const PTTCallView: React.FC = ({ const [containerRef, bounds] = useMeasure({ polyfill: ResizeObserver }); const facepileSize = bounds.width < 800 ? "sm" : "md"; const pttButtonSize = 232; - const pttBorderWidth = 6; const { audioOutput } = useMediaHandler(); @@ -135,13 +135,7 @@ export const PTTCallView: React.FC = ({ const activeSpeakerUser = activeSpeakerUserId ? client.getUser(activeSpeakerUserId) : null; - const activeSpeakerAvatarUrl = activeSpeakerUser - ? getAvatarUrl( - client, - activeSpeakerUser.avatarUrl, - pttButtonSize - pttBorderWidth * 2 - ) - : null; + const activeSpeakerAvatarUrl = activeSpeakerUser?.avatarUrl; const activeSpeakerDisplayName = activeSpeakerUser ? activeSpeakerUser.displayName : ""; @@ -156,7 +150,11 @@ export const PTTCallView: React.FC = ({ />
- +
diff --git a/src/room/VideoPreview.jsx b/src/room/VideoPreview.jsx index a68a4f4..761f949 100644 --- a/src/room/VideoPreview.jsx +++ b/src/room/VideoPreview.jsx @@ -62,12 +62,7 @@ export function VideoPreview({ {localVideoMuted && (
diff --git a/src/room/useRoomAvatar.ts b/src/room/useRoomAvatar.ts new file mode 100644 index 0000000..092bd73 --- /dev/null +++ b/src/room/useRoomAvatar.ts @@ -0,0 +1,24 @@ +import { useState, useEffect } from "react"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state"; +import { EventType } from "matrix-js-sdk/src/@types/event"; + +export const useRoomAvatar = (room: Room) => { + const [avatarUrl, setAvatarUrl] = useState(room.getMxcAvatarUrl()); + + useEffect(() => { + const update = (ev: MatrixEvent) => { + if (ev.getType() === EventType.RoomAvatar) { + setAvatarUrl(room.getMxcAvatarUrl()); + } + }; + + room.currentState.on(RoomStateEvent.Events, update); + return () => { + room.currentState.off(RoomStateEvent.Events, update); + }; + }, [room]); + + return avatarUrl; +};