Merge upstream v4.12.3 (Element Call 0.20.1) into lotus
CI / Build & Quality Checks (push) Successful in 10m42s
CI / Trigger Desktop Build (push) Successful in 10s

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 04:11:41 -04:00
13 changed files with 136 additions and 53 deletions
+8 -5
View File
@@ -129,6 +129,12 @@ export function CallControls({ callEmbed }: CallControlsProps) {
setCords(undefined);
};
const handleMicrophoneToggle = useCallback(
() => callEmbed.control.toggleMicrophone(),
[callEmbed],
);
const handleVideoToggle = useCallback(() => callEmbed.control.toggleVideo(), [callEmbed]);
const pttActiveRef = useRef(false);
useEffect(() => {
@@ -368,10 +374,7 @@ export function CallControls({ callEmbed }: CallControlsProps) {
>
<Box alignItems="Center" gap="Inherit" grow="Yes" direction={compact ? 'Column' : 'Row'}>
<Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
<MicrophoneButton
enabled={microphone}
onToggle={() => callEmbed.control.toggleMicrophone()}
/>
<MicrophoneButton enabled={microphone} onToggle={handleMicrophoneToggle} />
<SoundButton enabled={sound} onToggle={() => callEmbed.control.toggleSound()} />
<ScreenshareAudioButton
muted={screenshareAudioMuted}
@@ -380,7 +383,7 @@ export function CallControls({ callEmbed }: CallControlsProps) {
</Box>
{!compact && <ControlDivider />}
<Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
<VideoButton enabled={video} onToggle={() => callEmbed.control.toggleVideo()} />
<VideoButton enabled={video} onToggle={handleVideoToggle} />
<ScreenShareButton
enabled={screenshare}
onToggle={() =>
+13 -5
View File
@@ -3,6 +3,7 @@ import { Icon, IconButton, Icons, Line, Text, Tooltip, TooltipProvider } from 'f
import { useAtom } from 'jotai';
import * as css from './styles.css';
import { callChatAtom } from '../../state/callEmbed';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
export function ControlDivider() {
return (
@@ -12,9 +13,12 @@ export function ControlDivider() {
type MicrophoneButtonProps = {
enabled: boolean;
onToggle: () => void;
onToggle: () => Promise<unknown>;
};
export function MicrophoneButton({ enabled, onToggle }: MicrophoneButtonProps) {
const [micState, toggleMic] = useAsyncCallback(onToggle);
const loading = micState.status === AsyncStatus.Loading;
return (
<TooltipProvider
position="Top"
@@ -32,9 +36,10 @@ export function MicrophoneButton({ enabled, onToggle }: MicrophoneButtonProps) {
fill="Soft"
radii="400"
size="400"
onClick={() => onToggle()}
onClick={toggleMic}
aria-label={enabled ? 'Turn Off Microphone' : 'Turn On Microphone'}
outlined
disabled={loading}
>
<Icon size="400" src={enabled ? Icons.Mic : Icons.MicMute} filled={!enabled} />
</IconButton>
@@ -82,10 +87,13 @@ export function SoundButton({ enabled, onToggle }: SoundButtonProps) {
type VideoButtonProps = {
enabled: boolean;
onToggle: () => void;
onToggle: () => Promise<unknown>;
disabled?: boolean;
};
export function VideoButton({ enabled, onToggle, disabled }: VideoButtonProps) {
const [videoState, toggleVideo] = useAsyncCallback(onToggle);
const loading = videoState.status === AsyncStatus.Loading;
return (
<TooltipProvider
position="Top"
@@ -105,9 +113,9 @@ export function VideoButton({ enabled, onToggle, disabled }: VideoButtonProps) {
fill="Soft"
radii="400"
size="400"
onClick={() => onToggle()}
onClick={toggleVideo}
outlined
disabled={disabled}
disabled={disabled || loading}
aria-label={
disabled ? 'Camera disabled in settings' : enabled ? 'Stop camera' : 'Start camera'
}
+6 -3
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Box, Button, Icon, Icons, Spinner, Text } from 'folds';
import { SequenceCard } from '../../components/sequence-card';
import * as css from './styles.css';
@@ -54,6 +54,9 @@ export function PrescreenControls({ canJoin }: PrescreenControlsProps) {
useCallPreferences();
const [cameraOnJoin] = useSetting(settingsAtom, 'cameraOnJoin');
const handleMicrophoneToggle = useCallback(async () => toggleMicrophone(), [toggleMicrophone]);
const handleVideoToggle = useCallback(async () => toggleVideo(), [toggleVideo]);
return (
<SequenceCard
className={css.ControlCard}
@@ -65,12 +68,12 @@ export function PrescreenControls({ canJoin }: PrescreenControlsProps) {
wrap="Wrap"
>
<Box shrink="No" alignItems="Inherit" justifyContent="SpaceBetween" gap="200">
<MicrophoneButton enabled={microphone} onToggle={toggleMicrophone} />
<MicrophoneButton enabled={microphone} onToggle={handleMicrophoneToggle} />
<SoundButton enabled={sound} onToggle={toggleSound} />
</Box>
<ControlDivider />
<Box shrink="No" alignItems="Inherit" justifyContent="SpaceBetween" gap="200">
<VideoButton enabled={video} onToggle={toggleVideo} disabled={!cameraOnJoin} />
<VideoButton enabled={video} onToggle={handleVideoToggle} disabled={!cameraOnJoin} />
<ChatButton />
</Box>
<Box grow="Yes" direction="Column" gap="200">