Switch to Avatar from Compound
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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.
|
||||
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.
|
||||
*/
|
||||
|
||||
.avatar {
|
||||
position: relative;
|
||||
color: var(--stopgap-color-on-solid-accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar svg * {
|
||||
fill: var(--cpd-color-text-primary);
|
||||
}
|
||||
|
||||
.avatar span {
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.xs {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 22px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sm {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 32px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.md {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 36px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.lg {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 42px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.xl {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 90px;
|
||||
font-size: 48px;
|
||||
}
|
||||
@@ -14,23 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { useMemo, CSSProperties, HTMLAttributes, FC } from "react";
|
||||
import classNames from "classnames";
|
||||
import { useMemo, FC } from "react";
|
||||
import { Avatar as CompoundAvatar } from "@vector-im/compound-web";
|
||||
|
||||
import { getAvatarUrl } from "./matrix-utils";
|
||||
import { useClient } from "./ClientContext";
|
||||
import styles from "./Avatar.module.css";
|
||||
|
||||
const backgroundColors = [
|
||||
"#5C56F5",
|
||||
"#03B381",
|
||||
"#368BD6",
|
||||
"#AC3BA8",
|
||||
"#E64F7A",
|
||||
"#FF812D",
|
||||
"#2DC2C5",
|
||||
"#74D12C",
|
||||
];
|
||||
|
||||
export enum Size {
|
||||
XS = "xs",
|
||||
@@ -48,50 +36,28 @@ export const sizes = new Map([
|
||||
[Size.XL, 90],
|
||||
]);
|
||||
|
||||
function hashStringToArrIndex(str: string, arrLength: number) {
|
||||
let sum = 0;
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
sum += str.charCodeAt(i);
|
||||
}
|
||||
|
||||
return sum % arrLength;
|
||||
}
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
bgKey?: string;
|
||||
interface Props {
|
||||
id: string;
|
||||
name: string;
|
||||
className?: string;
|
||||
src?: string;
|
||||
size?: Size | number;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
fallback: string;
|
||||
}
|
||||
|
||||
export const Avatar: FC<Props> = ({
|
||||
bgKey,
|
||||
src,
|
||||
fallback,
|
||||
size = Size.MD,
|
||||
className,
|
||||
style = {},
|
||||
...rest
|
||||
id,
|
||||
name,
|
||||
src,
|
||||
size = Size.MD,
|
||||
}) => {
|
||||
const { client } = useClient();
|
||||
|
||||
const [sizeClass, sizePx, sizeStyle] = useMemo(
|
||||
const sizePx = 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),
|
||||
},
|
||||
],
|
||||
? sizes.get(size as Size)
|
||||
: (size as number),
|
||||
[size]
|
||||
);
|
||||
|
||||
@@ -100,28 +66,13 @@ export const Avatar: FC<Props> = ({
|
||||
return src.startsWith("mxc://") ? getAvatarUrl(client, src, sizePx) : src;
|
||||
}, [client, src, sizePx]);
|
||||
|
||||
const backgroundColor = useMemo(() => {
|
||||
const index = hashStringToArrIndex(
|
||||
bgKey || fallback || src || "",
|
||||
backgroundColors.length
|
||||
);
|
||||
return backgroundColors[index];
|
||||
}, [bgKey, src, fallback]);
|
||||
|
||||
/* eslint-disable jsx-a11y/alt-text */
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.avatar, sizeClass, className)}
|
||||
style={{ backgroundColor, ...sizeStyle, ...style }}
|
||||
{...rest}
|
||||
>
|
||||
{resolvedSrc ? (
|
||||
<img src={resolvedSrc} />
|
||||
) : typeof fallback === "string" ? (
|
||||
<span>{fallback}</span>
|
||||
) : (
|
||||
fallback
|
||||
)}
|
||||
</div>
|
||||
<CompoundAvatar
|
||||
className={className}
|
||||
id={id}
|
||||
name={name}
|
||||
size={`${sizePx}px`}
|
||||
src={resolvedSrc}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,17 +24,3 @@ limitations under the License.
|
||||
.userButton svg * {
|
||||
fill: var(--cpd-color-icon-primary);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font-size: var(--font-size-caption);
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
.avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: var(--font-size-body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ interface UserMenuProps {
|
||||
preventNavigation: boolean;
|
||||
isAuthenticated: boolean;
|
||||
isPasswordlessUser: boolean;
|
||||
userId: string;
|
||||
displayName: string;
|
||||
avatarUrl?: string;
|
||||
onAction: (value: string) => void;
|
||||
@@ -44,6 +45,7 @@ export function UserMenu({
|
||||
preventNavigation,
|
||||
isAuthenticated,
|
||||
isPasswordlessUser,
|
||||
userId,
|
||||
displayName,
|
||||
avatarUrl,
|
||||
onAction,
|
||||
@@ -109,10 +111,10 @@ export function UserMenu({
|
||||
>
|
||||
{isAuthenticated && (!isPasswordlessUser || avatarUrl) ? (
|
||||
<Avatar
|
||||
id={userId}
|
||||
name={displayName}
|
||||
size={Size.SM}
|
||||
className={styles.avatar}
|
||||
src={avatarUrl}
|
||||
fallback={displayName.slice(0, 1).toUpperCase()}
|
||||
/>
|
||||
) : (
|
||||
<UserIcon />
|
||||
|
||||
@@ -67,6 +67,7 @@ export function UserMenuContainer({ preventNavigation = false }: Props) {
|
||||
isPasswordlessUser={passwordlessUser}
|
||||
avatarUrl={avatarUrl}
|
||||
onAction={onAction}
|
||||
userId={client?.getUserId() ?? ""}
|
||||
displayName={displayName || (userName ? userName.replace("@", "") : "")}
|
||||
/>
|
||||
{modalState.isOpen && client && (
|
||||
|
||||
@@ -67,13 +67,7 @@ function CallTile({ name, avatarUrl, roomId }: CallTileProps) {
|
||||
return (
|
||||
<div className={styles.callTile}>
|
||||
<Link to={`/room/#?roomId=${roomId}`} className={styles.callTileLink}>
|
||||
<Avatar
|
||||
size={Size.LG}
|
||||
bgKey={name}
|
||||
src={avatarUrl}
|
||||
fallback={name.slice(0, 1).toUpperCase()}
|
||||
className={styles.avatar}
|
||||
/>
|
||||
<Avatar id={roomId} name={name} size={Size.LG} src={avatarUrl} />
|
||||
<div className={styles.callInfo}>
|
||||
<Body overflowEllipsis fontWeight="semiBold">
|
||||
{name}
|
||||
|
||||
@@ -35,13 +35,23 @@ interface Props extends AllHTMLAttributes<HTMLInputElement> {
|
||||
id: string;
|
||||
label: string;
|
||||
avatarUrl: string | undefined;
|
||||
userId: string;
|
||||
displayName: string;
|
||||
onRemoveAvatar: () => void;
|
||||
}
|
||||
|
||||
export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
||||
(
|
||||
{ id, label, className, avatarUrl, displayName, onRemoveAvatar, ...rest },
|
||||
{
|
||||
id,
|
||||
label,
|
||||
className,
|
||||
avatarUrl,
|
||||
userId,
|
||||
displayName,
|
||||
onRemoveAvatar,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -80,9 +90,10 @@ export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
||||
<div className={classNames(styles.avatarInputField, className)}>
|
||||
<div className={styles.avatarContainer}>
|
||||
<Avatar
|
||||
id={userId}
|
||||
name={displayName}
|
||||
size={Size.XL}
|
||||
src={removed ? undefined : objUrl || avatarUrl}
|
||||
fallback={displayName.slice(0, 1).toUpperCase()}
|
||||
/>
|
||||
<input
|
||||
id={id}
|
||||
|
||||
@@ -92,13 +92,14 @@ export function GroupCallView({
|
||||
const { displayName, avatarUrl } = useProfile(client);
|
||||
const matrixInfo = useMemo((): MatrixInfo => {
|
||||
return {
|
||||
userId: client.getUserId()!,
|
||||
displayName: displayName!,
|
||||
avatarUrl: avatarUrl!,
|
||||
roomId: groupCall.room.roomId,
|
||||
roomName: groupCall.room.name,
|
||||
roomAlias: groupCall.room.getCanonicalAlias(),
|
||||
};
|
||||
}, [displayName, avatarUrl, groupCall]);
|
||||
}, [client, displayName, avatarUrl, groupCall]);
|
||||
|
||||
const deviceContext = useMediaDevices();
|
||||
const latestDevices = useRef<MediaDevices>();
|
||||
|
||||
@@ -35,6 +35,7 @@ import { useMediaDevices } from "../livekit/MediaDevicesContext";
|
||||
import { MuteStates } from "./MuteStates";
|
||||
|
||||
export type MatrixInfo = {
|
||||
userId: string;
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
roomId: string;
|
||||
@@ -124,9 +125,10 @@ export const VideoPreview: FC<Props> = ({ matrixInfo, muteStates }) => {
|
||||
{!muteStates.video.enabled && (
|
||||
<div className={styles.avatarContainer}>
|
||||
<Avatar
|
||||
id={matrixInfo.userId}
|
||||
name={matrixInfo.displayName}
|
||||
size={(previewBounds.height - 66) / 2}
|
||||
src={matrixInfo.avatarUrl}
|
||||
fallback={matrixInfo.displayName.slice(0, 1).toUpperCase()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -29,6 +29,7 @@ interface Props {
|
||||
export function ProfileSettingsTab({ client }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { error, displayName, avatarUrl, saveProfile } = useProfile(client);
|
||||
const userId = useMemo(() => client.getUserId(), [client]);
|
||||
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
|
||||
@@ -77,12 +78,13 @@ export function ProfileSettingsTab({ client }: Props) {
|
||||
return (
|
||||
<form onChange={onFormChange} ref={formRef} className={styles.content}>
|
||||
<FieldRow className={styles.avatarFieldRow}>
|
||||
{displayName && (
|
||||
{userId && displayName && (
|
||||
<AvatarInputField
|
||||
id="avatar"
|
||||
name="avatar"
|
||||
label={t("Avatar")}
|
||||
avatarUrl={avatarUrl}
|
||||
userId={userId}
|
||||
displayName={displayName}
|
||||
onRemoveAvatar={onRemoveAvatar}
|
||||
/>
|
||||
|
||||
@@ -145,6 +145,8 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
||||
// Firefox doesn't respect the disablePictureInPicture attribute
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
|
||||
|
||||
console.log(`LOG VideoTIle mxcSrc=${member?.getMxcAvatarUrl()}`);
|
||||
|
||||
return (
|
||||
<animated.div
|
||||
className={classNames(styles.videoTile, className, {
|
||||
@@ -169,9 +171,10 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
||||
<div className={styles.videoMutedOverlay} />
|
||||
<Avatar
|
||||
key={member?.userId}
|
||||
id={member?.userId ?? displayName}
|
||||
name={displayName}
|
||||
size={Math.round(Math.min(targetWidth, targetHeight) / 2)}
|
||||
src={member?.getMxcAvatarUrl()}
|
||||
fallback={displayName.slice(0, 1).toUpperCase()}
|
||||
className={styles.avatar}
|
||||
/>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user