({
return (
- {springs.map((style, i) => {
+ {springs.map((spring, i) => {
const tile = tiles[i];
const tilePosition = tilePositions[tile.order];
- return createChild({
- ...bindTile(tile.key),
- ...style,
- key: tile.key,
- data: tile.item.data,
- targetWidth: tilePosition.width,
- targetHeight: tilePosition.height,
- });
+ return (
+
+ {children as (props: ChildrenProperties) => ReactNode}
+
+ );
})}
);
diff --git a/src/video-grid/VideoTile.module.css b/src/video-grid/VideoTile.module.css
index adcc57a..20849a9 100644
--- a/src/video-grid/VideoTile.module.css
+++ b/src/video-grid/VideoTile.module.css
@@ -18,12 +18,10 @@ limitations under the License.
position: absolute;
contain: strict;
top: 0;
- width: var(--tileWidth);
- height: var(--tileHeight);
+ container-name: videoTile;
+ container-type: size;
--tileRadius: 8px;
border-radius: var(--tileRadius);
- box-shadow: rgba(0, 0, 0, 0.5) 0px var(--tileShadow)
- calc(2 * var(--tileShadow)) var(--tileShadowSpread);
overflow: hidden;
cursor: pointer;
@@ -195,3 +193,20 @@ limitations under the License.
--tileRadius: 20px;
}
}
+
+/* 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 */
+@container videoTile (width > 0) {
+ .avatar {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ /* To make avatars scale smoothly with their tiles during animations, we
+ override the styles set on the element */
+ --avatarSize: 50cqmin; /* Half of the smallest dimension of the tile */
+ width: var(--avatarSize) !important;
+ height: var(--avatarSize) !important;
+ border-radius: 10000px !important;
+ }
+}
diff --git a/src/video-grid/VideoTile.tsx b/src/video-grid/VideoTile.tsx
index a9ec49a..703d3da 100644
--- a/src/video-grid/VideoTile.tsx
+++ b/src/video-grid/VideoTile.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2022 New Vector Ltd
+Copyright 2022-2023 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.
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, { ForwardedRef, forwardRef } from "react";
-import { animated, SpringValue } from "@react-spring/web";
+import React from "react";
+import { animated } from "@react-spring/web";
import classNames from "classnames";
import { useTranslation } from "react-i18next";
import { LocalParticipant, RemoteParticipant, Track } from "livekit-client";
@@ -24,59 +24,60 @@ import {
VideoTrack,
useMediaTrack,
} from "@livekit/components-react";
+import {
+ RoomMember,
+ RoomMemberEvent,
+} from "matrix-js-sdk/src/models/room-member";
+import { Avatar } from "../Avatar";
import styles from "./VideoTile.module.css";
import { ReactComponent as MicIcon } from "../icons/Mic.svg";
import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg";
+export interface ItemData {
+ member?: RoomMember;
+ sfuParticipant: LocalParticipant | RemoteParticipant;
+ content: TileContent;
+}
+
export enum TileContent {
UserMedia = "user-media",
ScreenShare = "screen-share",
}
interface Props {
- name: string;
- sfuParticipant: LocalParticipant | RemoteParticipant;
- content: TileContent;
+ data: ItemData;
- // TODO: Refactor this set of props.
- // See https://github.com/vector-im/element-call/pull/1099#discussion_r1226863404
- avatar?: JSX.Element;
+ // TODO: Refactor these props.
+ targetWidth: number;
+ targetHeight: number;
className?: string;
- opacity?: SpringValue;
- scale?: SpringValue;
- shadow?: SpringValue;
- shadowSpread?: SpringValue;
- zIndex?: SpringValue;
- x?: SpringValue;
- y?: SpringValue;
- width?: SpringValue;
- height?: SpringValue;
+ style?: React.ComponentProps["style"];
}
-export const VideoTile = forwardRef(
- (
- {
- name,
- sfuParticipant,
- content,
- avatar,
- className,
- opacity,
- scale,
- shadow,
- shadowSpread,
- zIndex,
- x,
- y,
- width,
- height,
- ...rest
- },
- ref
- ) => {
+export const VideoTile = React.forwardRef(
+ ({ data, className, style, targetWidth, targetHeight }, tileRef) => {
const { t } = useTranslation();
+ const { content, sfuParticipant, member } = data;
+
+ // Handle display name changes.
+ const [displayName, setDisplayName] = React.useState("[👻]");
+ React.useEffect(() => {
+ if (member) {
+ setDisplayName(member.rawDisplayName);
+
+ const updateName = () => {
+ setDisplayName(member.rawDisplayName);
+ };
+
+ member!.on(RoomMemberEvent.Name, updateName);
+ return () => {
+ member!.removeListener(RoomMemberEvent.Name, updateName);
+ };
+ }
+ }, [member]);
+
const audioEl = React.useRef(null);
const { isMuted: microphoneMuted } = useMediaTrack(
content === TileContent.UserMedia
@@ -88,6 +89,9 @@ export const VideoTile = forwardRef(
}
);
+ // Firefox doesn't respect the disablePictureInPicture attribute
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
+
return (
(
[styles.muted]: microphoneMuted,
[styles.screenshare]: content === TileContent.ScreenShare,
})}
- style={{
- opacity,
- scale,
- zIndex,
- x,
- y,
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore React does in fact support assigning custom properties,
- // but React's types say no
- "--tileWidth": width?.to((w) => `${w}px`),
- "--tileHeight": height?.to((h) => `${h}px`),
- "--tileShadow": shadow?.to((s) => `${s}px`),
- "--tileShadowSpread": shadowSpread?.to((s) => `${s}px`),
- }}
- ref={ref as ForwardedRef}
+ style={style}
+ ref={tileRef}
data-testid="videoTile"
- {...rest}
>
- {!sfuParticipant.isCameraEnabled && (
+ {content === TileContent.UserMedia && !sfuParticipant.isCameraEnabled && (
<>
- {avatar}
+
>
)}
- {sfuParticipant.isScreenShareEnabled ? (
+ {content == TileContent.ScreenShare ? (
- {t("{{name}} is presenting", { name })}
+ {t("{{displayName}} is presenting", { displayName })}
) : (
{microphoneMuted ? : }
- {name}
+ {displayName}
)}
diff --git a/src/video-grid/VideoTileContainer.tsx b/src/video-grid/VideoTileContainer.tsx
deleted file mode 100644
index 215535f..0000000
--- a/src/video-grid/VideoTileContainer.tsx
+++ /dev/null
@@ -1,115 +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 React from "react";
-import {
- RoomMember,
- RoomMemberEvent,
-} from "matrix-js-sdk/src/models/room-member";
-import { LocalParticipant, RemoteParticipant } from "livekit-client";
-import { SpringValue } from "@react-spring/web";
-import { EventTypes, Handler, useDrag } from "@use-gesture/react";
-
-import { TileContent, VideoTile } from "./VideoTile";
-import { Avatar } from "../Avatar";
-import Styles from "../room/InCallView.module.css";
-
-export interface ItemData {
- member?: RoomMember;
- sfuParticipant: LocalParticipant | RemoteParticipant;
- content: TileContent;
-}
-
-interface Props {
- item: ItemData;
-
- // TODO: Refactor this set of props.
- // See https://github.com/vector-im/element-call/pull/1099#discussion_r1226863404
- id: string;
- targetWidth: number;
- targetHeight: number;
- opacity?: SpringValue;
- scale?: SpringValue;
- shadow?: SpringValue;
- shadowSpread?: SpringValue;
- zIndex?: SpringValue;
- x?: SpringValue;
- y?: SpringValue;
- width?: SpringValue;
- height?: SpringValue;
- onDragRef?: React.RefObject<
- (
- tileId: string,
- state: Parameters>[0]
- ) => void
- >;
-}
-
-export const VideoTileContainer: React.FC = React.memo(
- ({ item, id, targetWidth, targetHeight, onDragRef, ...rest }) => {
- // Handle display name changes.
- const [displayName, setDisplayName] = React.useState("[👻]");
- React.useEffect(() => {
- const member = item.member;
-
- if (member) {
- setDisplayName(member.rawDisplayName);
-
- const updateName = () => {
- setDisplayName(member.rawDisplayName);
- };
-
- member!.on(RoomMemberEvent.Name, updateName);
- return () => {
- member!.removeListener(RoomMemberEvent.Name, updateName);
- };
- }
- }, [item.member]);
-
- // Create an avatar.
- const avatar = (
-
- );
-
- // Make sure that the tile is draggable and work well within video grid layout.
- //
- // Firefox doesn't respect the disablePictureInPicture attribute
- // https://bugzilla.mozilla.org/show_bug.cgi?id=1611831
- const tileRef = React.useRef(null);
- useDrag((state) => onDragRef?.current!(id, state), {
- target: tileRef,
- filterTaps: true,
- preventScroll: true,
- });
-
- return (
-
- );
- }
-);