diff --git a/src/room/InCallView.module.css b/src/room/InCallView.module.css index 0e510a7..5638b10 100644 --- a/src/room/InCallView.module.css +++ b/src/room/InCallView.module.css @@ -82,17 +82,21 @@ limitations under the License. bottom: 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: calc(min(var(--tileWidth), var(--tileHeight)) / 2); - width: var(--avatarSize) !important; - height: var(--avatarSize) !important; - border-radius: 10000px !important; +/* 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; + } } @media (min-height: 300px) { diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 8ecfafa..f257e45 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -28,7 +28,7 @@ import { Room, Track } from "livekit-client"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall"; -import React, { useCallback, useEffect, useMemo, useRef } from "react"; +import React, { Ref, useCallback, useEffect, useMemo, useRef } from "react"; import { useTranslation } from "react-i18next"; import useMeasure from "react-use-measure"; import { OverlayTriggerState } from "@react-stately/overlays"; @@ -61,24 +61,24 @@ import { useModalTriggerState } from "../Modal"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { useUrlParams } from "../UrlParams"; import { MediaDevicesState } from "../settings/mediaDevices"; -import { useRageshakeRequestModal } from "../settings/submit-rageshake"; import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts"; import { usePrefersReducedMotion } from "../usePrefersReducedMotion"; -import { ItemData, VideoTileContainer } from "../video-grid/VideoTileContainer"; import { ElementWidgetActions, widget } from "../widget"; import { GridLayoutMenu } from "./GridLayoutMenu"; import { GroupCallInspector } from "./GroupCallInspector"; import styles from "./InCallView.module.css"; -import { RageshakeRequestModal } from "./RageshakeRequestModal"; import { MatrixInfo } from "./VideoPreview"; import { useJoinRule } from "./useJoinRule"; import { ParticipantInfo } from "./useGroupCall"; -import { TileContent } from "../video-grid/VideoTile"; +import { ItemData, TileContent } from "../video-grid/VideoTile"; import { Config } from "../config/Config"; import { NewVideoGrid } from "../video-grid/NewVideoGrid"; import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; import { SettingsModal } from "../settings/SettingsModal"; import { InviteModal } from "./InviteModal"; +import { useRageshakeRequestModal } from "../settings/submit-rageshake"; +import { RageshakeRequestModal } from "./RageshakeRequestModal"; +import { VideoTile } from "../video-grid/VideoTile"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -291,14 +291,13 @@ export function InCallView({ } if (maximisedParticipant) { return ( - ); } @@ -309,11 +308,12 @@ export function InCallView({ layout={layout} disableAnimations={prefersReducedMotion || isSafari} > - {(child) => ( - ( + 2} + {...props} + ref={props.ref as Ref} /> )} diff --git a/src/useMergedRefs.ts b/src/useMergedRefs.ts index 3fab929..8139c15 100644 --- a/src/useMergedRefs.ts +++ b/src/useMergedRefs.ts @@ -21,14 +21,14 @@ import { MutableRefObject, RefCallback, useCallback } from "react"; * same DOM node. */ export const useMergedRefs = ( - ...refs: (MutableRefObject | RefCallback)[] + ...refs: (MutableRefObject | RefCallback | null)[] ): RefCallback => useCallback( (value) => refs.forEach((ref) => { if (typeof ref === "function") { ref(value); - } else { + } else if (ref !== null) { ref.current = value; } }), diff --git a/src/video-grid/NewVideoGrid.tsx b/src/video-grid/NewVideoGrid.tsx index 7833c14..d215e6a 100644 --- a/src/video-grid/NewVideoGrid.tsx +++ b/src/video-grid/NewVideoGrid.tsx @@ -18,7 +18,6 @@ import { SpringRef, TransitionFn, useTransition } from "@react-spring/web"; import { EventTypes, Handler, useScroll } from "@use-gesture/react"; import React, { Dispatch, - FC, ReactNode, SetStateAction, useCallback, @@ -35,6 +34,7 @@ import { VideoGridProps as Props, TileSpring, TileDescriptor, + ChildrenProperties, } from "./VideoGrid"; import { useReactiveState } from "../useReactiveState"; import { useMergedRefs } from "../useMergedRefs"; @@ -48,6 +48,7 @@ import { cycleTileSize, appendItems, } from "./model"; +import { TileWrapper } from "./TileWrapper"; interface GridState extends Grid { /** @@ -144,11 +145,11 @@ interface DragState { /** * An interactive, animated grid of video tiles. */ -export const NewVideoGrid: FC> = ({ +export function NewVideoGrid({ items, disableAnimations, children, -}) => { +}: Props) { // Overview: This component lays out tiles by rendering an invisible template // grid of "slots" for tiles to go in. Once rendered, it uses the DOM API to // get the dimensions of each slot, feeding these numbers back into @@ -455,16 +456,19 @@ export const NewVideoGrid: FC> = ({ > {slots} - {tileTransitions((style, tile) => - children({ - ...style, - key: tile.item.id, - targetWidth: tile.width, - targetHeight: tile.height, - data: tile.item.data, - onDragRef: onTileDragRef, - }) - )} + {tileTransitions((spring, tile) => ( + + {children as (props: ChildrenProperties) => ReactNode} + + ))} ); -}; +} diff --git a/src/video-grid/TileWrapper.tsx b/src/video-grid/TileWrapper.tsx new file mode 100644 index 0000000..b9f84b5 --- /dev/null +++ b/src/video-grid/TileWrapper.tsx @@ -0,0 +1,100 @@ +/* +Copyright 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. +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, { FC, memo, ReactNode, RefObject, useRef } from "react"; +import { EventTypes, Handler, useDrag } from "@use-gesture/react"; +import { SpringValue, to } from "@react-spring/web"; + +import { ChildrenProperties } from "./VideoGrid"; + +interface Props { + id: string; + onDragRef: RefObject< + ( + tileId: string, + state: Parameters>[0] + ) => void + >; + targetWidth: number; + targetHeight: number; + data: T; + opacity: SpringValue; + scale: SpringValue; + shadow: SpringValue; + shadowSpread: SpringValue; + zIndex: SpringValue; + x: SpringValue; + y: SpringValue; + width: SpringValue; + height: SpringValue; + children: (props: ChildrenProperties) => ReactNode; +} + +/** + * A wrapper around a tile in a video grid. This component exists to decouple + * child components from the grid. + */ +export const TileWrapper: FC> = memo( + ({ + id, + onDragRef, + targetWidth, + targetHeight, + data, + opacity, + scale, + shadow, + shadowSpread, + zIndex, + x, + y, + width, + height, + children, + }) => { + const ref = useRef(null); + + useDrag((state) => onDragRef?.current!(id, state), { + target: ref, + filterTaps: true, + preventScroll: true, + }); + + return ( + <> + {children({ + ref, + style: { + opacity, + scale, + zIndex, + x, + y, + width, + height, + boxShadow: to( + [shadow, shadowSpread], + (s, ss) => `rgba(0, 0, 0, 0.5) 0px ${s}px ${2 * s}px ${ss}px` + ), + }, + targetWidth, + targetHeight, + data, + })} + + ); + } +); diff --git a/src/video-grid/VideoGrid.stories.tsx b/src/video-grid/VideoGrid.stories.tsx deleted file mode 100644 index 2098ef3..0000000 --- a/src/video-grid/VideoGrid.stories.tsx +++ /dev/null @@ -1,61 +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, { useState } from "react"; - -import { useVideoGridLayout } from "./VideoGrid"; -import { Button } from "../button"; - -export default { - title: "VideoGrid", - parameters: { - layout: "fullscreen", - }, -}; - -const ParticipantsTest = () => { - const { layout, setLayout } = useVideoGridLayout(false); - const [participantCount, setParticipantCount] = useState(1); - - return ( - <> -
- - {participantCount < 12 && ( - - )} - {participantCount > 0 && ( - - )} -
- - ); -}; - -ParticipantsTest.args = { - layout: "freedom", - participantCount: 1, -}; diff --git a/src/video-grid/VideoGrid.tsx b/src/video-grid/VideoGrid.tsx index 15f4180..63c0c62 100644 --- a/src/video-grid/VideoGrid.tsx +++ b/src/video-grid/VideoGrid.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. @@ -15,8 +15,10 @@ limitations under the License. */ import React, { + ComponentProps, Key, - RefObject, + ReactNode, + Ref, useCallback, useEffect, useRef, @@ -26,21 +28,20 @@ import { EventTypes, FullGestureState, Handler, - useDrag, useGesture, } from "@use-gesture/react"; import { + animated, SpringRef, - SpringValue, SpringValues, useSprings, } from "@react-spring/web"; import useMeasure from "react-use-measure"; import { ResizeObserver as JuggleResizeObserver } from "@juggle/resize-observer"; -import { ReactDOMAttributes } from "@use-gesture/react/dist/declarations/src/types"; import styles from "./VideoGrid.module.css"; import { Layout } from "../room/GridLayoutMenu"; +import { TileWrapper } from "./TileWrapper"; interface TilePosition { x: number; @@ -51,7 +52,7 @@ interface TilePosition { } interface Tile { - key: Key; + key: string; order: number; item: TileDescriptor; remove: boolean; @@ -727,25 +728,18 @@ interface DragTileData { y: number; } -export interface ChildrenProperties extends ReactDOMAttributes { - key: Key; - data: T; +export interface ChildrenProperties { + ref: Ref; + style: ComponentProps["style"]; + /** + * The width this tile will have once its animations have settled. + */ targetWidth: number; + /** + * The height this tile will have once its animations have settled. + */ targetHeight: number; - opacity: SpringValue; - scale: SpringValue; - shadow: SpringValue; - zIndex: SpringValue; - x: SpringValue; - y: SpringValue; - width: SpringValue; - height: SpringValue; - onDragRef?: RefObject< - ( - tileId: string, - state: Parameters>[0] - ) => void - >; + data: T; } export interface VideoGridProps { @@ -768,7 +762,7 @@ export function VideoGrid({ items, layout, disableAnimations, - children: createChild, + children, }: VideoGridProps) { // Place the PiP in the bottom right corner by default const [pipXRatio, setPipXRatio] = useState(1); @@ -1082,117 +1076,132 @@ export function VideoGrid({ [tiles, layout, gridBounds.width, gridBounds.height, pipXRatio, pipYRatio] ); - const bindTile = useDrag( - ({ args: [key], active, xy, movement, tap, last, event }) => { - event.preventDefault(); + // Callback for useDrag. We could call useDrag here, but the default + // pattern of spreading {...bind()} across the children to bind the gesture + // ends up breaking memoization and ruining this component's performance. + // Instead, we pass this callback to each tile via a ref, to let them bind the + // gesture using the much more sensible ref-based method. + const onTileDrag = ( + tileId: string, + { + active, + xy, + movement, + tap, + last, + event, + }: Parameters>[0] + ) => { + event.preventDefault(); - if (tap) { - onTap(key); - return; - } + if (tap) { + onTap(tileId); + return; + } - if (layout !== "freedom") return; + if (layout !== "freedom") return; - const dragTileIndex = tiles.findIndex((tile) => tile.key === key); - const dragTile = tiles[dragTileIndex]; - const dragTilePosition = tilePositions[dragTile.order]; + const dragTileIndex = tiles.findIndex((tile) => tile.key === tileId); + const dragTile = tiles[dragTileIndex]; + const dragTilePosition = tilePositions[dragTile.order]; - const cursorPosition = [xy[0] - gridBounds.left, xy[1] - gridBounds.top]; + const cursorPosition = [xy[0] - gridBounds.left, xy[1] - gridBounds.top]; - let newTiles = tiles; + let newTiles = tiles; - if (tiles.length === 2 && !tiles.some((t) => t.focused)) { - // We're in 1:1 mode, so only the local tile should be draggable - if (!dragTile.item.local) return; + if (tiles.length === 2 && !tiles.some((t) => t.focused)) { + // We're in 1:1 mode, so only the local tile should be draggable + if (!dragTile.item.local) return; - // Position should only update on the very last event, to avoid - // compounding the offset on every drag event - if (last) { - const remotePosition = tilePositions[1]; + // Position should only update on the very last event, to avoid + // compounding the offset on every drag event + if (last) { + const remotePosition = tilePositions[1]; - const pipGap = getPipGap( - gridBounds.width / gridBounds.height, - gridBounds.width - ); - const pipMinX = remotePosition.x + pipGap; - const pipMinY = remotePosition.y + pipGap; - const pipMaxX = - remotePosition.x + - remotePosition.width - - dragTilePosition.width - - pipGap; - const pipMaxY = - remotePosition.y + - remotePosition.height - - dragTilePosition.height - - pipGap; - - const newPipXRatio = - (dragTilePosition.x + movement[0] - pipMinX) / (pipMaxX - pipMinX); - const newPipYRatio = - (dragTilePosition.y + movement[1] - pipMinY) / (pipMaxY - pipMinY); - - setPipXRatio(Math.max(0, Math.min(1, newPipXRatio))); - setPipYRatio(Math.max(0, Math.min(1, newPipYRatio))); - } - } else { - const hoverTile = tiles.find( - (tile) => - tile.key !== key && - isInside(cursorPosition, tilePositions[tile.order]) + const pipGap = getPipGap( + gridBounds.width / gridBounds.height, + gridBounds.width ); + const pipMinX = remotePosition.x + pipGap; + const pipMinY = remotePosition.y + pipGap; + const pipMaxX = + remotePosition.x + + remotePosition.width - + dragTilePosition.width - + pipGap; + const pipMaxY = + remotePosition.y + + remotePosition.height - + dragTilePosition.height - + pipGap; - if (hoverTile) { - // Shift the tiles into their new order - newTiles = newTiles.map((tile) => { - let order = tile.order; - if (order < dragTile.order) { - if (order >= hoverTile.order) order++; - } else if (order > dragTile.order) { - if (order <= hoverTile.order) order--; - } else { - order = hoverTile.order; - } + const newPipXRatio = + (dragTilePosition.x + movement[0] - pipMinX) / (pipMaxX - pipMinX); + const newPipYRatio = + (dragTilePosition.y + movement[1] - pipMinY) / (pipMaxY - pipMinY); - let focused; - if (tile === hoverTile) { - focused = dragTile.focused; - } else if (tile === dragTile) { - focused = hoverTile.focused; - } else { - focused = tile.focused; - } - - return { ...tile, order, focused }; - }); - - reorderTiles(newTiles, layout); - - setTileState((state) => ({ ...state, tiles: newTiles })); - } + setPipXRatio(Math.max(0, Math.min(1, newPipXRatio))); + setPipYRatio(Math.max(0, Math.min(1, newPipYRatio))); } + } else { + const hoverTile = tiles.find( + (tile) => + tile.key !== tileId && + isInside(cursorPosition, tilePositions[tile.order]) + ); - if (active) { - if (!draggingTileRef.current) { - draggingTileRef.current = { - key: dragTile.key, - offsetX: dragTilePosition.x, - offsetY: dragTilePosition.y, - x: movement[0], - y: movement[1], - }; - } else { - draggingTileRef.current.x = movement[0]; - draggingTileRef.current.y = movement[1]; - } + if (hoverTile) { + // Shift the tiles into their new order + newTiles = newTiles.map((tile) => { + let order = tile.order; + if (order < dragTile.order) { + if (order >= hoverTile.order) order++; + } else if (order > dragTile.order) { + if (order <= hoverTile.order) order--; + } else { + order = hoverTile.order; + } + + let focused; + if (tile === hoverTile) { + focused = dragTile.focused; + } else if (tile === dragTile) { + focused = hoverTile.focused; + } else { + focused = tile.focused; + } + + return { ...tile, order, focused }; + }); + + reorderTiles(newTiles, layout); + + setTileState((state) => ({ ...state, tiles: newTiles })); + } + } + + if (active) { + if (!draggingTileRef.current) { + draggingTileRef.current = { + key: dragTile.key, + offsetX: dragTilePosition.x, + offsetY: dragTilePosition.y, + x: movement[0], + y: movement[1], + }; } else { - draggingTileRef.current = null; + draggingTileRef.current.x = movement[0]; + draggingTileRef.current.y = movement[1]; } + } else { + draggingTileRef.current = null; + } - api.start(animate(newTiles)); - }, - { filterTaps: true, pointer: { buttons: [1] } } - ); + api.start(animate(newTiles)); + }; + + const onTileDragRef = useRef(onTileDrag); + onTileDragRef.current = onTileDrag; const onGridGesture = useCallback( ( @@ -1239,18 +1248,23 @@ export function VideoGrid({ 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..62d4545 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; diff --git a/src/video-grid/VideoTile.tsx b/src/video-grid/VideoTile.tsx index f73590d..beff7ec 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, { ComponentProps, forwardRef } 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,10 +24,19 @@ import { VideoTrack, useMediaTrack, } from "@livekit/components-react"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import styles from "./VideoTile.module.css"; import { ReactComponent as MicIcon } from "../icons/Mic.svg"; import { ReactComponent as MicMutedIcon } from "../icons/MicMuted.svg"; +import { useRoomMemberName } from "./useRoomMemberName"; + +export interface ItemData { + id: string; + member: RoomMember; + sfuParticipant: LocalParticipant | RemoteParticipant; + content: TileContent; +} export enum TileContent { UserMedia = "user-media", @@ -35,52 +44,37 @@ export enum TileContent { } interface Props { - avatar?: JSX.Element; + data: ItemData; className?: string; - name: string; - sfuParticipant: LocalParticipant | RemoteParticipant; - content: TileContent; - showOptions?: boolean; - isLocal?: boolean; - disableSpeakingIndicator?: boolean; - opacity?: SpringValue; - scale?: SpringValue; - shadow?: SpringValue; - shadowSpread?: SpringValue; - zIndex?: SpringValue; - x?: SpringValue; - y?: SpringValue; - width?: SpringValue; - height?: SpringValue; + showSpeakingIndicator: boolean; + style?: ComponentProps["style"]; + targetWidth: number; + targetHeight: number; + getAvatar: ( + roomMember: RoomMember, + width: number, + height: number + ) => JSX.Element; } -export const VideoTile = forwardRef( +export const VideoTile = forwardRef( ( { - name, - sfuParticipant, - content, - avatar, + data, className, - showOptions, - isLocal, - // TODO: disableSpeakingIndicator is not used atm. - disableSpeakingIndicator, - opacity, - scale, - shadow, - shadowSpread, - zIndex, - x, - y, - width, - height, - ...rest + showSpeakingIndicator, + style, + targetWidth, + targetHeight, + getAvatar, }, - ref + tileRef ) => { const { t } = useTranslation(); + const { content, sfuParticipant } = data; + const { rawDisplayName: name } = useRoomMemberName(data.member); + const audioEl = React.useRef(null); const { isMuted: microphoneMuted } = useMediaTrack( content === TileContent.UserMedia @@ -92,36 +86,25 @@ export const VideoTile = forwardRef( } ); + // Firefox doesn't respect the disablePictureInPicture attribute + // https://bugzilla.mozilla.org/show_bug.cgi?id=1611831 + return ( `${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 && ( <>
- {avatar} + {getAvatar(data.member, targetWidth, targetHeight)} )} {!false && diff --git a/src/video-grid/VideoTileContainer.tsx b/src/video-grid/VideoTileContainer.tsx deleted file mode 100644 index 9be2bff..0000000 --- a/src/video-grid/VideoTileContainer.tsx +++ /dev/null @@ -1,86 +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 { LocalParticipant, RemoteParticipant } from "livekit-client"; -import React, { FC, memo, RefObject, useRef } from "react"; -import { RoomMember } from "matrix-js-sdk/src/models/room-member"; -import { SpringValue } from "@react-spring/web"; -import { EventTypes, Handler, useDrag } from "@use-gesture/react"; - -import { useRoomMemberName } from "./useRoomMemberName"; -import { TileContent, VideoTile } from "./VideoTile"; - -export interface ItemData { - id: string; - member: RoomMember; - sfuParticipant: LocalParticipant | RemoteParticipant; - content: TileContent; -} - -interface Props { - item: ItemData; - targetWidth: number; - targetHeight: number; - getAvatar: ( - roomMember: RoomMember, - width: number, - height: number - ) => JSX.Element; - disableSpeakingIndicator: boolean; - maximised: boolean; - opacity?: SpringValue; - scale?: SpringValue; - shadow?: SpringValue; - shadowSpread?: SpringValue; - zIndex?: SpringValue; - x?: SpringValue; - y?: SpringValue; - width?: SpringValue; - height?: SpringValue; - onDragRef?: RefObject< - ( - tileId: string, - state: Parameters>[0] - ) => void - >; -} - -export const VideoTileContainer: FC = memo( - ({ item, targetWidth, targetHeight, getAvatar, onDragRef, ...rest }) => { - const { rawDisplayName } = useRoomMemberName(item.member); - const tileRef = useRef(null); - - useDrag((state) => onDragRef?.current!(item.id, state), { - target: tileRef, - filterTaps: true, - preventScroll: true, - }); - - // Firefox doesn't respect the disablePictureInPicture attribute - // https://bugzilla.mozilla.org/show_bug.cgi?id=1611831 - - return ( - - ); - } -);