- Implemented DockedCallPanel component to render the docked call interface when an active call is present. - Created RemoteCursorOverlay component to display remote cursor positions during screen sharing. - Added hooks for managing docked call state and remote cursor functionality. - Introduced pending drag state management for seamless transitions between docked and floating states. - Developed embed filter management components for personal and room-wide settings, allowing users to customize URL embed visibility. - Implemented auto-joining functionality for rooms within spaces, enhancing user experience during space navigation. - Added state management for tracking joining progress of rooms in spaces.
133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
import React from 'react';
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
|
import { getMemberDisplayName } from '../../utils/room';
|
|
import { getMxIdLocalPart } from '../../utils/matrix';
|
|
import { useRoomCallMembers } from './useRoomCallMembers';
|
|
import { useCall } from './useCall';
|
|
import { useSendCursor, useReceiveCursors, RemoteCursorState } from './useRemoteCursor';
|
|
import colorMXID from '../../../util/colorMXID';
|
|
import * as css from './CallOverlay.css';
|
|
|
|
/** Props for RemoteCursorOverlay */
|
|
interface RemoteCursorOverlayProps {
|
|
/** The screen share ID to track cursors for */
|
|
shareId: string;
|
|
/** The room ID of the active call */
|
|
roomId: string;
|
|
}
|
|
|
|
/**
|
|
* Resolves a LiveKit participant identity to a Matrix user ID
|
|
* LiveKit identities are typically in the format "deviceId_timestamp"
|
|
*/
|
|
function resolveParticipantUserId(
|
|
livekitIdentity: string,
|
|
callMembers: { userId: string; deviceId: string }[]
|
|
): string {
|
|
const underscoreIndex = livekitIdentity.lastIndexOf('_');
|
|
const possibleDeviceId =
|
|
underscoreIndex > 0 ? livekitIdentity.substring(0, underscoreIndex) : livekitIdentity;
|
|
|
|
const match = callMembers.find((m) => m.deviceId === possibleDeviceId);
|
|
return match?.userId ?? livekitIdentity;
|
|
}
|
|
|
|
/**
|
|
* Single remote cursor dot with label
|
|
*/
|
|
function CursorDot({
|
|
cursor,
|
|
displayName,
|
|
cursorColor,
|
|
}: {
|
|
cursor: RemoteCursorState;
|
|
displayName: string;
|
|
cursorColor: string;
|
|
}) {
|
|
if (!cursor.visible) return null;
|
|
|
|
const left = `${cursor.x * 100}%`;
|
|
const top = `${cursor.y * 100}%`;
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
className={css.RemoteCursorDot}
|
|
style={{
|
|
left,
|
|
top,
|
|
backgroundColor: cursorColor,
|
|
opacity: cursor.visible ? 1 : 0,
|
|
}}
|
|
/>
|
|
<div
|
|
className={css.RemoteCursorLabel}
|
|
style={{ left, top, color: 'white' }}
|
|
>
|
|
{displayName}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Overlay that renders remote cursor positions on top of a screen share
|
|
* Also provides mouse event handlers for sending local cursor position
|
|
*/
|
|
export function RemoteCursorOverlay({ shareId, roomId }: RemoteCursorOverlayProps) {
|
|
const mx = useMatrixClient();
|
|
const { callMembers } = useRoomCallMembers(roomId);
|
|
const { activeCall } = useCall();
|
|
const cursors = useReceiveCursors(shareId);
|
|
const room = mx.getRoom(roomId);
|
|
const myUserId = mx.getUserId() ?? '';
|
|
|
|
/**
|
|
* Gets a display name for a LiveKit participant
|
|
*/
|
|
const getDisplayName = (livekitIdentity: string): string => {
|
|
const userId = resolveParticipantUserId(livekitIdentity, callMembers);
|
|
if (!room) return getMxIdLocalPart(userId) ?? userId;
|
|
return getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId;
|
|
};
|
|
|
|
// Filter out our own cursor and only show visible cursors
|
|
const myIdentity = activeCall?.participants.find((p) => {
|
|
const underscoreIndex = p.lastIndexOf('_');
|
|
const deviceId = underscoreIndex > 0 ? p.substring(0, underscoreIndex) : p;
|
|
const member = callMembers.find((m) => m.deviceId === deviceId);
|
|
return member?.userId === myUserId;
|
|
});
|
|
|
|
const remoteCursors = Array.from(cursors.entries()).filter(
|
|
([participantId]) => participantId !== myIdentity
|
|
);
|
|
|
|
if (remoteCursors.length === 0) return null;
|
|
|
|
return (
|
|
<div className={css.RemoteCursorOverlay}>
|
|
{remoteCursors.map(([participantId, cursor]) => {
|
|
const userId = resolveParticipantUserId(participantId, callMembers);
|
|
return (
|
|
<CursorDot
|
|
key={participantId}
|
|
cursor={cursor}
|
|
displayName={getDisplayName(participantId)}
|
|
cursorColor={colorMXID(userId)}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Wrapper that provides mouse event handlers for cursor sending on a screenshare
|
|
* @param shareId - The screen share ID
|
|
* @returns Mouse event handlers to attach to the container
|
|
*/
|
|
export function useScreenShareCursor(shareId: string) {
|
|
return useSendCursor(shareId);
|
|
}
|