Merge pull request #1436 from robintown/video-tile-1
First pass at the new video tile designs
This commit is contained in:
@@ -116,6 +116,7 @@
|
|||||||
"Walkie-talkie call": "Walkie-talkie call",
|
"Walkie-talkie call": "Walkie-talkie call",
|
||||||
"Walkie-talkie call name": "Walkie-talkie call name",
|
"Walkie-talkie call name": "Walkie-talkie call name",
|
||||||
"Yes, join call": "Yes, join call",
|
"Yes, join call": "Yes, join call",
|
||||||
|
"You": "You",
|
||||||
"You were disconnected from the call": "You were disconnected from the call",
|
"You were disconnected from the call": "You were disconnected from the call",
|
||||||
"Your feedback": "Your feedback",
|
"Your feedback": "Your feedback",
|
||||||
"Your recent calls": "Your recent calls"
|
"Your recent calls": "Your recent calls"
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import { LoginPage } from "./auth/LoginPage";
|
|||||||
import { RegisterPage } from "./auth/RegisterPage";
|
import { RegisterPage } from "./auth/RegisterPage";
|
||||||
import { RoomPage } from "./room/RoomPage";
|
import { RoomPage } from "./room/RoomPage";
|
||||||
import { ClientProvider } from "./ClientContext";
|
import { ClientProvider } from "./ClientContext";
|
||||||
import { usePageFocusStyle } from "./usePageFocusStyle";
|
|
||||||
import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
|
import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
|
||||||
import { InspectorContextProvider } from "./room/GroupCallInspector";
|
import { InspectorContextProvider } from "./room/GroupCallInspector";
|
||||||
import { CrashView, LoadingView } from "./FullScreenView";
|
import { CrashView, LoadingView } from "./FullScreenView";
|
||||||
@@ -48,8 +47,6 @@ export default function App({ history }: AppProps) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
usePageFocusStyle();
|
|
||||||
|
|
||||||
const errorPage = <CrashView />;
|
const errorPage = <CrashView />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -50,14 +50,14 @@ limitations under the License.
|
|||||||
background-color: var(--cpd-color-text-action-accent);
|
background-color: var(--cpd-color-text-action-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:focus,
|
.button:focus-visible,
|
||||||
.toolbarButton:focus,
|
.toolbarButton:focus-visible,
|
||||||
.toolbarButtonSecondary:focus,
|
.toolbarButtonSecondary:focus-visible,
|
||||||
.iconButton:focus,
|
.iconButton:focus-visible,
|
||||||
.iconCopyButton:focus,
|
.iconCopyButton:focus-visible,
|
||||||
.secondary:focus,
|
.secondary:focus-visible,
|
||||||
.secondaryHangup:focus,
|
.secondaryHangup:focus-visible,
|
||||||
.copyButton:focus {
|
.copyButton:focus-visible {
|
||||||
outline: auto;
|
outline: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ limitations under the License.
|
|||||||
background-color: var(--cpd-color-bg-canvas-default);
|
background-color: var(--cpd-color-bg-canvas-default);
|
||||||
color: var(--cpd-color-icon-primary);
|
color: var(--cpd-color-icon-primary);
|
||||||
border: 1px solid var(--cpd-color-gray-400);
|
border: 1px solid var(--cpd-color-gray-400);
|
||||||
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
box-shadow: var(--subtle-drop-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbarButton.on,
|
.toolbarButton.on,
|
||||||
|
|||||||
@@ -39,19 +39,22 @@ limitations under the License.
|
|||||||
--font-size-title: calc(24px * var(--font-scale));
|
--font-size-title: calc(24px * var(--font-scale));
|
||||||
--font-size-headline: calc(32px * var(--font-scale));
|
--font-size-headline: calc(32px * var(--font-scale));
|
||||||
|
|
||||||
--cpd-color-border-accent: var(--cpd-color-green-1100);
|
|
||||||
/* These colors are needed during the transitionary period between the old and
|
/* These colors are needed during the transitionary period between the old and
|
||||||
new Compound design systems, but should be removed ASAP */
|
new Compound design systems, but should be removed ASAP */
|
||||||
--stopgap-color-on-solid-accent: var(--cpd-color-bg-canvas-default);
|
--stopgap-color-on-solid-accent: var(--cpd-color-bg-canvas-default);
|
||||||
--stopgap-background-85: rgba(255, 255, 255, 0.85);
|
--stopgap-background-85: rgba(255, 255, 255, 0.85);
|
||||||
--stopgap-bgColor3: #444;
|
--stopgap-bgColor3: #444;
|
||||||
|
|
||||||
|
--cpd-color-border-accent: var(--cpd-color-green-800);
|
||||||
/* The distance to inset non-full-width content from the edge of the window
|
/* The distance to inset non-full-width content from the edge of the window
|
||||||
along the inline axis */
|
along the inline axis */
|
||||||
--inline-content-inset: max(var(--cpd-space-4x), calc((100vw - 1180px) / 2));
|
--inline-content-inset: max(var(--cpd-space-4x), calc((100vw - 1180px) / 2));
|
||||||
|
--small-drop-shadow: 0px 1.2px 2.4px 0px rgba(0, 0, 0, 0.15);
|
||||||
|
--subtle-drop-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cpd-theme-dark {
|
.cpd-theme-dark {
|
||||||
|
--cpd-color-border-accent: var(--cpd-color-green-1100);
|
||||||
--stopgap-color-on-solid-accent: var(--cpd-color-text-primary);
|
--stopgap-color-on-solid-accent: var(--cpd-color-text-primary);
|
||||||
--stopgap-background-85: rgba(16, 19, 23, 0.85);
|
--stopgap-background-85: rgba(16, 19, 23, 0.85);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ limitations under the License.
|
|||||||
border-radius: var(--cpd-radius-pill-effect);
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
color: var(--cpd-color-icon-primary);
|
color: var(--cpd-color-icon-primary);
|
||||||
background: var(--cpd-color-bg-action-secondary-rest);
|
background: var(--cpd-color-bg-action-secondary-rest);
|
||||||
box-shadow: 0px 1.2px 2.4px 0px rgba(0, 0, 0, 0.15);
|
box-shadow: var(--small-drop-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
|
|||||||
@@ -1,19 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.hideFocus * {
|
|
||||||
outline: none !important;
|
|
||||||
}
|
|
||||||
@@ -1,39 +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 { useEffect } from "react";
|
|
||||||
import { useFocusVisible } from "@react-aria/interactions";
|
|
||||||
|
|
||||||
import styles from "./usePageFocusStyle.module.css";
|
|
||||||
|
|
||||||
export function usePageFocusStyle(): void {
|
|
||||||
const { isFocusVisible } = useFocusVisible();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const classList = document.body.classList;
|
|
||||||
const hasClass = classList.contains(styles.hideFocus);
|
|
||||||
|
|
||||||
if (isFocusVisible && hasClass) {
|
|
||||||
classList.remove(styles.hideFocus);
|
|
||||||
} else if (!isFocusVisible && !hasClass) {
|
|
||||||
classList.add(styles.hideFocus);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
classList.remove(styles.hideFocus);
|
|
||||||
};
|
|
||||||
}, [isFocusVisible]);
|
|
||||||
}
|
|
||||||
@@ -16,14 +16,13 @@ limitations under the License.
|
|||||||
|
|
||||||
.bigGrid {
|
.bigGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-rows: 163px;
|
grid-auto-rows: 130px;
|
||||||
gap: 8px;
|
gap: var(--cpd-space-2x);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
@media (min-width: 800px) {
|
||||||
.bigGrid {
|
.bigGrid {
|
||||||
grid-auto-rows: 183px;
|
grid-auto-rows: 135px;
|
||||||
column-gap: 18px;
|
gap: var(--cpd-space-5x);
|
||||||
row-gap: 21px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -957,7 +957,7 @@ function updateTiles(g: Grid, tiles: TileDescriptor<unknown>[]): Grid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateBounds(g: Grid, bounds: RectReadOnly): Grid {
|
function updateBounds(g: Grid, bounds: RectReadOnly): Grid {
|
||||||
const columns = Math.max(2, Math.floor(bounds.width * 0.0045));
|
const columns = Math.max(2, Math.floor(bounds.width * 0.0055));
|
||||||
return columns === g.columns ? g : resize(g, columns);
|
return columns === g.columns ? g : resize(g, columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ export function NewVideoGrid<T>({
|
|||||||
from: ({ x, y, width, height }: Tile<T>) => ({
|
from: ({ x, y, width, height }: Tile<T>) => ({
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
scale: 0,
|
scale: 0,
|
||||||
shadow: 1,
|
shadow: 0,
|
||||||
shadowSpread: 0,
|
shadowSpread: 0,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
x,
|
x,
|
||||||
@@ -221,7 +221,7 @@ export function NewVideoGrid<T>({
|
|||||||
? {
|
? {
|
||||||
scale: 1,
|
scale: 1,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
shadow: 1,
|
shadow: 0,
|
||||||
x: tile.x,
|
x: tile.x,
|
||||||
y: tile.y,
|
y: tile.y,
|
||||||
width: tile.width,
|
width: tile.width,
|
||||||
|
|||||||
@@ -1082,7 +1082,7 @@ export function VideoGrid<T>({
|
|||||||
y?: number;
|
y?: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
} = { shadow: 1, scale: 0, opacity: 0 };
|
} = { shadow: 0, scale: 0, opacity: 0 };
|
||||||
let reset = false;
|
let reset = false;
|
||||||
|
|
||||||
if (!tilePositionsWereValid) {
|
if (!tilePositionsWereValid) {
|
||||||
@@ -1105,7 +1105,7 @@ export function VideoGrid<T>({
|
|||||||
scale: remove ? 0 : 1,
|
scale: remove ? 0 : 1,
|
||||||
opacity: remove ? 0 : 1,
|
opacity: remove ? 0 : 1,
|
||||||
zIndex: tilePosition.zIndex,
|
zIndex: tilePosition.zIndex,
|
||||||
shadow: 1,
|
shadow: oneOnOneLayout && tile.item.local ? 1 : 0,
|
||||||
shadowSpread: oneOnOneLayout && tile.item.local ? 1 : 0,
|
shadowSpread: oneOnOneLayout && tile.item.local ? 1 : 0,
|
||||||
from,
|
from,
|
||||||
reset,
|
reset,
|
||||||
|
|||||||
@@ -20,14 +20,11 @@ limitations under the License.
|
|||||||
top: 0;
|
top: 0;
|
||||||
container-name: videoTile;
|
container-name: videoTile;
|
||||||
container-type: size;
|
container-type: size;
|
||||||
--tileRadius: 8px;
|
border-radius: var(--cpd-space-4x);
|
||||||
border-radius: var(--tileRadius);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
outline: 2px solid rgba(0, 0, 0, 0);
|
||||||
/* HACK: This has no visual effect due to the short duration, but allows the
|
transition: outline-radius ease 0.15s, outline-color ease 0.15s;
|
||||||
JS to detect movement via the transform property for audio spatialization */
|
|
||||||
transition: transform 0.000000001s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.videoTile * {
|
.videoTile * {
|
||||||
@@ -45,21 +42,14 @@ limitations under the License.
|
|||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.videoTile::after {
|
.videoTile.speaking {
|
||||||
position: absolute;
|
outline: 4px solid var(--cpd-color-border-accent);
|
||||||
top: -1px;
|
|
||||||
left: -1px;
|
|
||||||
right: -1px;
|
|
||||||
bottom: -1px;
|
|
||||||
content: "";
|
|
||||||
border-radius: var(--tileRadius);
|
|
||||||
box-shadow: inset 0 0 0 4px var(--cpd-color-border-accent) !important;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity ease 0.15s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.videoTile.speaking::after {
|
@media (hover: hover) {
|
||||||
opacity: 1;
|
.videoTile:hover {
|
||||||
|
outline: 2px solid var(--cpd-color-gray-1400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.videoTile.maximised {
|
.videoTile.maximised {
|
||||||
@@ -73,30 +63,47 @@ limitations under the License.
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoBubble {
|
.nameTag {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 24px;
|
inset-inline-start: var(--cpd-space-1x);
|
||||||
padding: 0 8px;
|
inset-block-end: var(--cpd-space-1x);
|
||||||
|
padding: var(--cpd-space-1x);
|
||||||
|
padding-block: var(--cpd-space-1x);
|
||||||
color: var(--cpd-color-text-primary);
|
color: var(--cpd-color-text-primary);
|
||||||
background-color: var(--stopgap-background-85);
|
/* TODO: un-hardcode this color. It comes from the dark theme. */
|
||||||
|
background-color: rgba(237, 244, 252, 0.79);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
border-radius: 4px;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
max-width: calc(100% - 48px);
|
max-width: calc(100% - 48px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
box-shadow: var(--small-drop-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoBubble > svg {
|
:global(.cpd-theme-dark) .nameTag {
|
||||||
height: 16px;
|
/* TODO: un-hardcode this color. It comes from the light theme. */
|
||||||
width: 16px;
|
background-color: rgba(2, 7, 13, 0.77);
|
||||||
margin-right: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.infoBubble > svg * {
|
.nameTag > svg {
|
||||||
fill: var(--cpd-color-icon-primary);
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameTag > svg[data-muted="true"] {
|
||||||
|
color: var(--cpd-color-icon-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameTag > svg[data-muted="false"] {
|
||||||
|
color: var(--cpd-color-icon-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameTag span {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-inline: var(--cpd-space-2x);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
@@ -137,24 +144,6 @@ limitations under the License.
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.memberName {
|
|
||||||
left: 16px;
|
|
||||||
bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.memberName > :last-child {
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.memberName span {
|
|
||||||
font-size: var(--font-size-caption);
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: var(--font-size-body);
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.videoMutedOverlay {
|
.videoMutedOverlay {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -192,12 +181,6 @@ limitations under the License.
|
|||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
|
||||||
.videoTile {
|
|
||||||
--tileRadius: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CSS makes us put a condition here, even though all we want to do is
|
/* CSS makes us put a condition here, even though all we want to do is
|
||||||
unconditionally select the container so we can use cqmin units */
|
unconditionally select the container so we can use cqmin units */
|
||||||
@container videoTile (width > 0) {
|
@container videoTile (width > 0) {
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ import {
|
|||||||
RoomMember,
|
RoomMember,
|
||||||
RoomMemberEvent,
|
RoomMemberEvent,
|
||||||
} from "matrix-js-sdk/src/models/room-member";
|
} from "matrix-js-sdk/src/models/room-member";
|
||||||
|
import { ReactComponent as MicOnSolidIcon } from "@vector-im/compound-design-tokens/icons/mic-on-solid.svg";
|
||||||
|
import { ReactComponent as MicOffSolidIcon } from "@vector-im/compound-design-tokens/icons/mic-off-solid.svg";
|
||||||
|
import { Text } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import { Avatar } from "../Avatar";
|
import { Avatar } from "../Avatar";
|
||||||
import styles from "./VideoTile.module.css";
|
import styles from "./VideoTile.module.css";
|
||||||
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
|
|
||||||
import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg";
|
|
||||||
import { useReactiveState } from "../useReactiveState";
|
import { useReactiveState } from "../useReactiveState";
|
||||||
import { AudioButton, FullscreenButton } from "../button/Button";
|
import { AudioButton, FullscreenButton } from "../button/Button";
|
||||||
import { useModalTriggerState } from "../Modal";
|
import { useModalTriggerState } from "../Modal";
|
||||||
@@ -102,12 +103,15 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
}
|
}
|
||||||
}, [member, setDisplayName]);
|
}, [member, setDisplayName]);
|
||||||
|
|
||||||
const { isMuted: microphoneMuted } = useMediaTrack(
|
const muted =
|
||||||
content === TileContent.UserMedia
|
useMediaTrack(
|
||||||
? Track.Source.Microphone
|
content === TileContent.UserMedia
|
||||||
: Track.Source.ScreenShareAudio,
|
? Track.Source.Microphone
|
||||||
sfuParticipant
|
: Track.Source.ScreenShareAudio,
|
||||||
);
|
sfuParticipant
|
||||||
|
).isMuted !== false;
|
||||||
|
|
||||||
|
const MicIcon = muted ? MicOffSolidIcon : MicOnSolidIcon;
|
||||||
|
|
||||||
const onFullscreen = useCallback(() => {
|
const onFullscreen = useCallback(() => {
|
||||||
onToggleFullscreen(data.id);
|
onToggleFullscreen(data.id);
|
||||||
@@ -153,7 +157,6 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
sfuParticipant.isSpeaking &&
|
sfuParticipant.isSpeaking &&
|
||||||
content === TileContent.UserMedia &&
|
content === TileContent.UserMedia &&
|
||||||
showSpeakingIndicator,
|
showSpeakingIndicator,
|
||||||
[styles.muted]: microphoneMuted,
|
|
||||||
[styles.screenshare]: content === TileContent.ScreenShare,
|
[styles.screenshare]: content === TileContent.ScreenShare,
|
||||||
[styles.maximised]: maximised,
|
[styles.maximised]: maximised,
|
||||||
})}
|
})}
|
||||||
@@ -182,9 +185,16 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
|||||||
<span>{t("{{displayName}} is presenting", { displayName })}</span>
|
<span>{t("{{displayName}} is presenting", { displayName })}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={classNames(styles.infoBubble, styles.memberName)}>
|
<div className={styles.nameTag}>
|
||||||
{microphoneMuted === false ? <MicIcon /> : <MicMutedIcon />}
|
<MicIcon
|
||||||
<span title={displayName}>{displayName}</span>
|
width={20}
|
||||||
|
height={20}
|
||||||
|
aria-label={muted ? t("Microphone off") : t("Microphone on")}
|
||||||
|
data-muted={muted}
|
||||||
|
/>
|
||||||
|
<Text as="span" size="sm" weight="medium">
|
||||||
|
{sfuParticipant.isLocal ? t("You") : displayName}
|
||||||
|
</Text>
|
||||||
{showConnectionStats && (
|
{showConnectionStats && (
|
||||||
<ConnectionQualityIndicator participant={sfuParticipant} />
|
<ConnectionQualityIndicator participant={sfuParticipant} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user