fix(calls): "Focus camera" toggles to "Unfocus"; pin cleared on dispose; drop dead setPipMode
clearFocusParticipant() had no callers, so a spotlight pin was permanent. CallControl now tracks focusedUserId, the member menu toggles, and dispose() clears the pin. Removes _pipMode/setPipMode (never read). Fixes #56 Fixes #59 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Box, config, Icon, Icons, Menu, MenuItem, PopOut, RectCords, Text } from 'folds';
|
||||
import { CallMembership } from 'matrix-js-sdk/lib/matrixrtc/CallMembership';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
import { UserAvatar } from '../../components/user-avatar';
|
||||
@@ -12,8 +12,31 @@ import { StackedAvatar } from '../../components/stacked-avatar';
|
||||
import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { CallEmbed } from '../../plugins/call/CallEmbed';
|
||||
import { CallControlEvent } from '../../plugins/call/CallControl';
|
||||
import * as css from './styles.css';
|
||||
|
||||
// [Gitea #56] Subscribes to CallControl's focus pin so the menu can render a
|
||||
// "Focus camera" / "Unfocus camera" toggle instead of a one-way pin.
|
||||
function useFocusedUserId(callEmbed?: CallEmbed): string | null {
|
||||
const control = callEmbed?.control;
|
||||
const [focusedUserId, setFocusedUserId] = useState<string | null>(control?.focusedUserId ?? null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!control) {
|
||||
setFocusedUserId(null);
|
||||
return undefined;
|
||||
}
|
||||
setFocusedUserId(control.focusedUserId);
|
||||
const handleUpdate = () => setFocusedUserId(control.focusedUserId);
|
||||
control.on(CallControlEvent.StateUpdate, handleUpdate);
|
||||
return () => {
|
||||
control.off(CallControlEvent.StateUpdate, handleUpdate);
|
||||
};
|
||||
}, [control]);
|
||||
|
||||
return focusedUserId;
|
||||
}
|
||||
|
||||
type ParticipantMenuProps = {
|
||||
anchor: RectCords;
|
||||
name: string;
|
||||
@@ -33,15 +56,23 @@ function ParticipantMenu({
|
||||
profileCords,
|
||||
}: ParticipantMenuProps) {
|
||||
const openUserProfile = useOpenUserRoomProfile();
|
||||
const focusedUserId = useFocusedUserId(callEmbed);
|
||||
const isFocused = focusedUserId === userId;
|
||||
|
||||
const handleViewProfile = () => {
|
||||
onClose();
|
||||
openUserProfile(room.roomId, undefined, userId, profileCords, 'Top');
|
||||
};
|
||||
|
||||
// [Gitea #56] Toggle: focusing the already-focused participant clears the
|
||||
// pin and returns EC to speaker-follows, instead of leaving no way back.
|
||||
const handleFocusCamera = () => {
|
||||
onClose();
|
||||
callEmbed?.control.focusCameraParticipant(userId);
|
||||
if (isFocused) {
|
||||
callEmbed?.control.clearFocusParticipant();
|
||||
} else {
|
||||
callEmbed?.control.focusCameraParticipant(userId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -78,7 +109,7 @@ function ParticipantMenu({
|
||||
before={<Icon size="100" src={Icons.VideoCamera} />}
|
||||
onClick={handleFocusCamera}
|
||||
>
|
||||
<Text size="B300">Focus camera</Text>
|
||||
<Text size="B300">{isFocused ? 'Unfocus camera' : 'Focus camera'}</Text>
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem
|
||||
|
||||
Reference in New Issue
Block a user