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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useMemo, CSSProperties, HTMLAttributes, FC } from "react";
|
import { useMemo, FC } from "react";
|
||||||
import classNames from "classnames";
|
import { Avatar as CompoundAvatar } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import { getAvatarUrl } from "./matrix-utils";
|
import { getAvatarUrl } from "./matrix-utils";
|
||||||
import { useClient } from "./ClientContext";
|
import { useClient } from "./ClientContext";
|
||||||
import styles from "./Avatar.module.css";
|
|
||||||
|
|
||||||
const backgroundColors = [
|
|
||||||
"#5C56F5",
|
|
||||||
"#03B381",
|
|
||||||
"#368BD6",
|
|
||||||
"#AC3BA8",
|
|
||||||
"#E64F7A",
|
|
||||||
"#FF812D",
|
|
||||||
"#2DC2C5",
|
|
||||||
"#74D12C",
|
|
||||||
];
|
|
||||||
|
|
||||||
export enum Size {
|
export enum Size {
|
||||||
XS = "xs",
|
XS = "xs",
|
||||||
@@ -48,50 +36,28 @@ export const sizes = new Map([
|
|||||||
[Size.XL, 90],
|
[Size.XL, 90],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function hashStringToArrIndex(str: string, arrLength: number) {
|
interface Props {
|
||||||
let sum = 0;
|
id: string;
|
||||||
|
name: string;
|
||||||
for (let i = 0; i < str.length; i++) {
|
className?: string;
|
||||||
sum += str.charCodeAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum % arrLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
||||||
bgKey?: string;
|
|
||||||
src?: string;
|
src?: string;
|
||||||
size?: Size | number;
|
size?: Size | number;
|
||||||
className?: string;
|
|
||||||
style?: CSSProperties;
|
|
||||||
fallback: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Avatar: FC<Props> = ({
|
export const Avatar: FC<Props> = ({
|
||||||
bgKey,
|
|
||||||
src,
|
|
||||||
fallback,
|
|
||||||
size = Size.MD,
|
|
||||||
className,
|
className,
|
||||||
style = {},
|
id,
|
||||||
...rest
|
name,
|
||||||
|
src,
|
||||||
|
size = Size.MD,
|
||||||
}) => {
|
}) => {
|
||||||
const { client } = useClient();
|
const { client } = useClient();
|
||||||
|
|
||||||
const [sizeClass, sizePx, sizeStyle] = useMemo(
|
const sizePx = useMemo(
|
||||||
() =>
|
() =>
|
||||||
Object.values(Size).includes(size as Size)
|
Object.values(Size).includes(size as Size)
|
||||||
? [styles[size as string], sizes.get(size as Size), {}]
|
? sizes.get(size as Size)
|
||||||
: [
|
: (size as number),
|
||||||
null,
|
|
||||||
size as number,
|
|
||||||
{
|
|
||||||
width: size,
|
|
||||||
height: size,
|
|
||||||
borderRadius: size,
|
|
||||||
fontSize: Math.round((size as number) / 2),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[size]
|
[size]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -100,28 +66,13 @@ export const Avatar: FC<Props> = ({
|
|||||||
return src.startsWith("mxc://") ? getAvatarUrl(client, src, sizePx) : src;
|
return src.startsWith("mxc://") ? getAvatarUrl(client, src, sizePx) : src;
|
||||||
}, [client, src, sizePx]);
|
}, [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 (
|
return (
|
||||||
<div
|
<CompoundAvatar
|
||||||
className={classNames(styles.avatar, sizeClass, className)}
|
className={className}
|
||||||
style={{ backgroundColor, ...sizeStyle, ...style }}
|
id={id}
|
||||||
{...rest}
|
name={name}
|
||||||
>
|
size={`${sizePx}px`}
|
||||||
{resolvedSrc ? (
|
src={resolvedSrc}
|
||||||
<img src={resolvedSrc} />
|
/>
|
||||||
) : typeof fallback === "string" ? (
|
|
||||||
<span>{fallback}</span>
|
|
||||||
) : (
|
|
||||||
fallback
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,17 +24,3 @@ limitations under the License.
|
|||||||
.userButton svg * {
|
.userButton svg * {
|
||||||
fill: var(--cpd-color-icon-primary);
|
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;
|
preventNavigation: boolean;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
isPasswordlessUser: boolean;
|
isPasswordlessUser: boolean;
|
||||||
|
userId: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
onAction: (value: string) => void;
|
onAction: (value: string) => void;
|
||||||
@@ -44,6 +45,7 @@ export function UserMenu({
|
|||||||
preventNavigation,
|
preventNavigation,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isPasswordlessUser,
|
isPasswordlessUser,
|
||||||
|
userId,
|
||||||
displayName,
|
displayName,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
onAction,
|
onAction,
|
||||||
@@ -109,10 +111,10 @@ export function UserMenu({
|
|||||||
>
|
>
|
||||||
{isAuthenticated && (!isPasswordlessUser || avatarUrl) ? (
|
{isAuthenticated && (!isPasswordlessUser || avatarUrl) ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
|
id={userId}
|
||||||
|
name={displayName}
|
||||||
size={Size.SM}
|
size={Size.SM}
|
||||||
className={styles.avatar}
|
|
||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
fallback={displayName.slice(0, 1).toUpperCase()}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<UserIcon />
|
<UserIcon />
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export function UserMenuContainer({ preventNavigation = false }: Props) {
|
|||||||
isPasswordlessUser={passwordlessUser}
|
isPasswordlessUser={passwordlessUser}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
onAction={onAction}
|
onAction={onAction}
|
||||||
|
userId={client?.getUserId() ?? ""}
|
||||||
displayName={displayName || (userName ? userName.replace("@", "") : "")}
|
displayName={displayName || (userName ? userName.replace("@", "") : "")}
|
||||||
/>
|
/>
|
||||||
{modalState.isOpen && client && (
|
{modalState.isOpen && client && (
|
||||||
|
|||||||
@@ -67,13 +67,7 @@ function CallTile({ name, avatarUrl, roomId }: CallTileProps) {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.callTile}>
|
<div className={styles.callTile}>
|
||||||
<Link to={`/room/#?roomId=${roomId}`} className={styles.callTileLink}>
|
<Link to={`/room/#?roomId=${roomId}`} className={styles.callTileLink}>
|
||||||
<Avatar
|
<Avatar id={roomId} name={name} size={Size.LG} src={avatarUrl} />
|
||||||
size={Size.LG}
|
|
||||||
bgKey={name}
|
|
||||||
src={avatarUrl}
|
|
||||||
fallback={name.slice(0, 1).toUpperCase()}
|
|
||||||
className={styles.avatar}
|
|
||||||
/>
|
|
||||||
<div className={styles.callInfo}>
|
<div className={styles.callInfo}>
|
||||||
<Body overflowEllipsis fontWeight="semiBold">
|
<Body overflowEllipsis fontWeight="semiBold">
|
||||||
{name}
|
{name}
|
||||||
|
|||||||
@@ -35,13 +35,23 @@ interface Props extends AllHTMLAttributes<HTMLInputElement> {
|
|||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
avatarUrl: string | undefined;
|
avatarUrl: string | undefined;
|
||||||
|
userId: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
onRemoveAvatar: () => void;
|
onRemoveAvatar: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
||||||
(
|
(
|
||||||
{ id, label, className, avatarUrl, displayName, onRemoveAvatar, ...rest },
|
{
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
className,
|
||||||
|
avatarUrl,
|
||||||
|
userId,
|
||||||
|
displayName,
|
||||||
|
onRemoveAvatar,
|
||||||
|
...rest
|
||||||
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -80,9 +90,10 @@ export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
|||||||
<div className={classNames(styles.avatarInputField, className)}>
|
<div className={classNames(styles.avatarInputField, className)}>
|
||||||
<div className={styles.avatarContainer}>
|
<div className={styles.avatarContainer}>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
id={userId}
|
||||||
|
name={displayName}
|
||||||
size={Size.XL}
|
size={Size.XL}
|
||||||
src={removed ? undefined : objUrl || avatarUrl}
|
src={removed ? undefined : objUrl || avatarUrl}
|
||||||
fallback={displayName.slice(0, 1).toUpperCase()}
|
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
id={id}
|
id={id}
|
||||||
|
|||||||
@@ -92,13 +92,14 @@ export function GroupCallView({
|
|||||||
const { displayName, avatarUrl } = useProfile(client);
|
const { displayName, avatarUrl } = useProfile(client);
|
||||||
const matrixInfo = useMemo((): MatrixInfo => {
|
const matrixInfo = useMemo((): MatrixInfo => {
|
||||||
return {
|
return {
|
||||||
|
userId: client.getUserId()!,
|
||||||
displayName: displayName!,
|
displayName: displayName!,
|
||||||
avatarUrl: avatarUrl!,
|
avatarUrl: avatarUrl!,
|
||||||
roomId: groupCall.room.roomId,
|
roomId: groupCall.room.roomId,
|
||||||
roomName: groupCall.room.name,
|
roomName: groupCall.room.name,
|
||||||
roomAlias: groupCall.room.getCanonicalAlias(),
|
roomAlias: groupCall.room.getCanonicalAlias(),
|
||||||
};
|
};
|
||||||
}, [displayName, avatarUrl, groupCall]);
|
}, [client, displayName, avatarUrl, groupCall]);
|
||||||
|
|
||||||
const deviceContext = useMediaDevices();
|
const deviceContext = useMediaDevices();
|
||||||
const latestDevices = useRef<MediaDevices>();
|
const latestDevices = useRef<MediaDevices>();
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { useMediaDevices } from "../livekit/MediaDevicesContext";
|
|||||||
import { MuteStates } from "./MuteStates";
|
import { MuteStates } from "./MuteStates";
|
||||||
|
|
||||||
export type MatrixInfo = {
|
export type MatrixInfo = {
|
||||||
|
userId: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
roomId: string;
|
roomId: string;
|
||||||
@@ -124,9 +125,10 @@ export const VideoPreview: FC<Props> = ({ matrixInfo, muteStates }) => {
|
|||||||
{!muteStates.video.enabled && (
|
{!muteStates.video.enabled && (
|
||||||
<div className={styles.avatarContainer}>
|
<div className={styles.avatarContainer}>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
id={matrixInfo.userId}
|
||||||
|
name={matrixInfo.displayName}
|
||||||
size={(previewBounds.height - 66) / 2}
|
size={(previewBounds.height - 66) / 2}
|
||||||
src={matrixInfo.avatarUrl}
|
src={matrixInfo.avatarUrl}
|
||||||
fallback={matrixInfo.displayName.slice(0, 1).toUpperCase()}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
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 { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ interface Props {
|
|||||||
export function ProfileSettingsTab({ client }: Props) {
|
export function ProfileSettingsTab({ client }: Props) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { error, displayName, avatarUrl, saveProfile } = useProfile(client);
|
const { error, displayName, avatarUrl, saveProfile } = useProfile(client);
|
||||||
|
const userId = useMemo(() => client.getUserId(), [client]);
|
||||||
|
|
||||||
const formRef = useRef<HTMLFormElement | null>(null);
|
const formRef = useRef<HTMLFormElement | null>(null);
|
||||||
|
|
||||||
@@ -77,12 +78,13 @@ export function ProfileSettingsTab({ client }: Props) {
|
|||||||
return (
|
return (
|
||||||
<form onChange={onFormChange} ref={formRef} className={styles.content}>
|
<form onChange={onFormChange} ref={formRef} className={styles.content}>
|
||||||
<FieldRow className={styles.avatarFieldRow}>
|
<FieldRow className={styles.avatarFieldRow}>
|
||||||
{displayName && (
|
{userId && displayName && (
|
||||||
<AvatarInputField
|
<AvatarInputField
|
||||||
id="avatar"
|
id="avatar"
|
||||||
name="avatar"
|
name="avatar"
|
||||||
label={t("Avatar")}
|
label={t("Avatar")}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
|
userId={userId}
|
||||||
displayName={displayName}
|
displayName={displayName}
|
||||||
onRemoveAvatar={onRemoveAvatar}
|
onRemoveAvatar={onRemoveAvatar}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
// Firefox doesn't respect the disablePictureInPicture attribute
|
// Firefox doesn't respect the disablePictureInPicture attribute
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
|
||||||
|
|
||||||
|
console.log(`LOG VideoTIle mxcSrc=${member?.getMxcAvatarUrl()}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<animated.div
|
<animated.div
|
||||||
className={classNames(styles.videoTile, className, {
|
className={classNames(styles.videoTile, className, {
|
||||||
@@ -169,9 +171,10 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
<div className={styles.videoMutedOverlay} />
|
<div className={styles.videoMutedOverlay} />
|
||||||
<Avatar
|
<Avatar
|
||||||
key={member?.userId}
|
key={member?.userId}
|
||||||
|
id={member?.userId ?? displayName}
|
||||||
|
name={displayName}
|
||||||
size={Math.round(Math.min(targetWidth, targetHeight) / 2)}
|
size={Math.round(Math.min(targetWidth, targetHeight) / 2)}
|
||||||
src={member?.getMxcAvatarUrl()}
|
src={member?.getMxcAvatarUrl()}
|
||||||
fallback={displayName.slice(0, 1).toUpperCase()}
|
|
||||||
className={styles.avatar}
|
className={styles.avatar}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user