Merge remote-tracking branch 'origin/livekit' into dbkr/matrixrtcsession
This commit is contained in:
@@ -3,7 +3,6 @@
|
|||||||
"{{count}} stars|other": "{{count}} stars",
|
"{{count}} stars|other": "{{count}} stars",
|
||||||
"{{displayName}} is presenting": "{{displayName}} is presenting",
|
"{{displayName}} is presenting": "{{displayName}} is presenting",
|
||||||
"{{displayName}}, your call has ended.": "{{displayName}}, your call has ended.",
|
"{{displayName}}, your call has ended.": "{{displayName}}, your call has ended.",
|
||||||
"{{names}}, {{name}}": "{{names}}, {{name}}",
|
|
||||||
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.",
|
"<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.": "<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.",
|
||||||
"<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>": "<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>",
|
"<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>": "<0>Already have an account?</0><1><0>Log in</0> Or <2>Access as a guest</2></1>",
|
||||||
"<0>Create an account</0> Or <2>Access as a guest</2>": "<0>Create an account</0> Or <2>Access as a guest</2>",
|
"<0>Create an account</0> Or <2>Access as a guest</2>": "<0>Create an account</0> Or <2>Access as a guest</2>",
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,42 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.facepile {
|
|
||||||
width: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.facepile.xs {
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.facepile.sm {
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.facepile.md {
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.facepile .avatar {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
border: 1px solid var(--cpd-color-bg-canvas-default);
|
|
||||||
}
|
|
||||||
|
|
||||||
.facepile.md .avatar {
|
|
||||||
border-width: 2px;
|
|
||||||
}
|
|
||||||
@@ -1,97 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { HTMLAttributes, useMemo } from "react";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import styles from "./Facepile.module.css";
|
|
||||||
import { Avatar, Size, sizes } from "./Avatar";
|
|
||||||
|
|
||||||
const overlapMap: Partial<Record<Size, number>> = {
|
|
||||||
[Size.XS]: 2,
|
|
||||||
[Size.SM]: 4,
|
|
||||||
[Size.MD]: 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
||||||
className: string;
|
|
||||||
client: MatrixClient;
|
|
||||||
members: RoomMember[];
|
|
||||||
max?: number;
|
|
||||||
size?: Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Facepile({
|
|
||||||
className,
|
|
||||||
client,
|
|
||||||
members,
|
|
||||||
max = 3,
|
|
||||||
size = Size.XS,
|
|
||||||
...rest
|
|
||||||
}: Props) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const _size = sizes.get(size)!;
|
|
||||||
const _overlap = overlapMap[size]!;
|
|
||||||
|
|
||||||
const title = useMemo(() => {
|
|
||||||
return members.reduce<string | null>(
|
|
||||||
(prev, curr) =>
|
|
||||||
prev === null
|
|
||||||
? curr.name
|
|
||||||
: t("{{names}}, {{name}}", { names: prev, name: curr.name }),
|
|
||||||
null
|
|
||||||
) as string;
|
|
||||||
}, [members, t]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames(styles.facepile, styles[size], className)}
|
|
||||||
title={title}
|
|
||||||
style={{
|
|
||||||
width:
|
|
||||||
Math.min(members.length, max + 1) * (_size - _overlap) + _overlap,
|
|
||||||
}}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{members.slice(0, max).map((member, i) => {
|
|
||||||
const avatarUrl = member.getMxcAvatarUrl();
|
|
||||||
return (
|
|
||||||
<Avatar
|
|
||||||
key={member.userId}
|
|
||||||
size={size}
|
|
||||||
src={avatarUrl ?? undefined}
|
|
||||||
fallback={member.name.slice(0, 1).toUpperCase()}
|
|
||||||
className={styles.avatar}
|
|
||||||
style={{ left: i * (_size - _overlap) }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{members.length > max && (
|
|
||||||
<Avatar
|
|
||||||
key="additional"
|
|
||||||
size={size}
|
|
||||||
fallback={`+${members.length - max}`}
|
|
||||||
className={styles.avatar}
|
|
||||||
style={{ left: max * (_size - _overlap) }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</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 && (
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
|
|||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
|
|
||||||
import { CopyButton } from "../button";
|
import { CopyButton } from "../button";
|
||||||
import { Facepile } from "../Facepile";
|
|
||||||
import { Avatar, Size } from "../Avatar";
|
import { Avatar, Size } from "../Avatar";
|
||||||
import styles from "./CallList.module.css";
|
import styles from "./CallList.module.css";
|
||||||
import { getRoomUrl } from "../matrix-utils";
|
import { getRoomUrl } from "../matrix-utils";
|
||||||
@@ -30,9 +29,8 @@ import { useRoomSharedKey } from "../e2ee/sharedKeyManagement";
|
|||||||
interface CallListProps {
|
interface CallListProps {
|
||||||
rooms: GroupCallRoom[];
|
rooms: GroupCallRoom[];
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
disableFacepile?: boolean;
|
|
||||||
}
|
}
|
||||||
export function CallList({ rooms, client, disableFacepile }: CallListProps) {
|
export function CallList({ rooms, client }: CallListProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.callList}>
|
<div className={styles.callList}>
|
||||||
@@ -44,7 +42,6 @@ export function CallList({ rooms, client, disableFacepile }: CallListProps) {
|
|||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
roomId={room.roomId}
|
roomId={room.roomId}
|
||||||
participants={participants}
|
participants={participants}
|
||||||
disableFacepile={disableFacepile}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{rooms.length > 3 && (
|
{rooms.length > 3 && (
|
||||||
@@ -63,39 +60,18 @@ interface CallTileProps {
|
|||||||
roomId: string;
|
roomId: string;
|
||||||
participants: RoomMember[];
|
participants: RoomMember[];
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
disableFacepile?: boolean;
|
|
||||||
}
|
}
|
||||||
function CallTile({
|
function CallTile({ name, avatarUrl, roomId }: CallTileProps) {
|
||||||
name,
|
|
||||||
avatarUrl,
|
|
||||||
roomId,
|
|
||||||
participants,
|
|
||||||
client,
|
|
||||||
disableFacepile,
|
|
||||||
}: CallTileProps) {
|
|
||||||
const roomSharedKey = useRoomSharedKey(roomId);
|
const roomSharedKey = useRoomSharedKey(roomId);
|
||||||
|
|
||||||
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}
|
||||||
</Body>
|
</Body>
|
||||||
{participants && !disableFacepile && (
|
|
||||||
<Facepile
|
|
||||||
className={styles.facePile}
|
|
||||||
client={client}
|
|
||||||
members={participants}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.copyButtonSpacer} />
|
<div className={styles.copyButtonSpacer} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
|||||||
<Title className={styles.recentCallsTitle}>
|
<Title className={styles.recentCallsTitle}>
|
||||||
{t("Your recent calls")}
|
{t("Your recent calls")}
|
||||||
</Title>
|
</Title>
|
||||||
<CallList rooms={recentRooms} client={client} disableFacepile />
|
<CallList rooms={recentRooms} client={client} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -84,13 +84,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: rtcSession.room.roomId,
|
roomId: rtcSession.room.roomId,
|
||||||
roomName: rtcSession.room.name,
|
roomName: rtcSession.room.name,
|
||||||
roomAlias: rtcSession.room.getCanonicalAlias(),
|
roomAlias: rtcSession.room.getCanonicalAlias(),
|
||||||
};
|
};
|
||||||
}, [displayName, avatarUrl, rtcSession]);
|
}, [client, displayName, avatarUrl, rtcSession]);
|
||||||
|
|
||||||
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;
|
||||||
@@ -129,9 +130,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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -169,9 +169,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