import { Box, Chip, Icon, IconButton, Icons, Spinner, Text, Tooltip, TooltipProvider } from 'folds'; import React, { useCallback, useState } from 'react'; import { useSetAtom } from 'jotai'; import { StatusDivider } from './components'; import { CallEmbed, useCallControlState } from '../../plugins/call'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { callEmbedAtom } from '../../state/callEmbed'; import { MobileTouchTarget } from '../../styles/mobile.css'; import { useRoomCallPolicy } from '../../hooks/useRoomCallPolicy'; import { ScreenshareConfirm } from '../call/ScreenshareConfirm'; type MicrophoneButtonProps = { enabled: boolean; onToggle: () => Promise; disabled?: boolean; }; function MicrophoneButton({ enabled, onToggle, disabled }: MicrophoneButtonProps) { const [micState, toggleMic] = useAsyncCallback(onToggle); const loading = micState.status === AsyncStatus.Loading; return ( {enabled ? 'Turn Off Microphone' : 'Turn On Microphone'} } > {(anchorRef) => ( )} ); } type SoundButtonProps = { enabled: boolean; onToggle: () => void; disabled?: boolean; }; function SoundButton({ enabled, onToggle, disabled }: SoundButtonProps) { return ( {enabled ? 'Turn Off Sound' : 'Turn On Sound'} } > {(anchorRef) => ( onToggle()} aria-label={enabled ? 'Deafen' : 'Undeafen'} aria-pressed={enabled} outlined disabled={disabled} > )} ); } type VideoButtonProps = { enabled: boolean; onToggle: () => Promise; disabled?: boolean; }; function VideoButton({ enabled, onToggle, disabled }: VideoButtonProps) { const [videoState, toggleVideo] = useAsyncCallback(onToggle); const loading = videoState.status === AsyncStatus.Loading; return ( {enabled ? 'Stop Camera' : 'Start Camera'} } > {(anchorRef) => ( )} ); } type ScreenShareButtonProps = { enabled: boolean; onToggle: () => void; disabled?: boolean; }; function ScreenShareButton({ enabled, onToggle, disabled }: ScreenShareButtonProps) { return ( {enabled ? 'Stop Screenshare' : 'Start Screenshare'} } > {(anchorRef) => ( )} ); } export function CallControl({ callEmbed, compact, callJoined, }: { callEmbed: CallEmbed; compact: boolean; callJoined: boolean; }) { const { microphone, video, sound, screenshare } = useCallControlState(callEmbed.control); const setCallEmbed = useSetAtom(callEmbedAtom); // [Gitea #26] Apply the same room-level camera/screenshare policy as the // in-room CallControls bar, so the status bar can't be used to bypass it. const { allowCamera, allowScreenshare } = useRoomCallPolicy(callEmbed.room); // Keep a forbidden control visible while its track is still live (so the user // can stop it); otherwise hide it entirely. const showCamera = allowCamera || video; const showScreenshare = allowScreenshare || screenshare; const [shareConfirm, setShareConfirm] = useState(false); const handleMicrophoneToggle = useCallback( () => callEmbed.control.toggleMicrophone(), [callEmbed], ); const handleVideoToggle = useCallback(() => callEmbed.control.toggleVideo(), [callEmbed]); const [hangupState, hangup] = useAsyncCallback( useCallback(() => callEmbed.hangup(), [callEmbed]), ); const exiting = hangupState.status === AsyncStatus.Loading || hangupState.status === AsyncStatus.Success; const handleHangup = () => { if (!callJoined) { setCallEmbed(undefined); return; } hangup(); }; return ( { callEmbed.control.toggleScreenshare(); setShareConfirm(false); }} onCancel={() => setShareConfirm(false)} /> callEmbed.control.toggleSound()} disabled={!callJoined} /> {!compact && (showCamera || showScreenshare) && } {showCamera && ( )} {!compact && showScreenshare && ( screenshare ? callEmbed.control.toggleScreenshare() : setShareConfirm(true) } disabled={!callJoined} /> )} ) : ( ) } disabled={exiting} outlined onClick={handleHangup} > {!compact && ( End )} ); }