Files
cinny/src/app/hooks/useTauriThumbbar.ts
T
jared 258e3ec620
CI / Build & Quality Checks (push) Successful in 10m40s
CI / Trigger Desktop Build (push) Successful in 8s
fix(desktop): address code-review findings on the desktop wave
- fileEntries: a single unreadable file/dir in a dropped folder no longer aborts
  the whole traversal (try/catch per entry, skip failures) — was discarding ALL
  dropped files (incl. the flat-file path) + an unhandled rejection; also add
  .catch in both useFileDrop consumers.
- RoomInput: mirror a localStorage-restored draft into the draft atom so the
  P5-57 indicator reflects a persisted draft after a page reload, not only on
  same-session room re-entry.
- useTauriThumbbar: swallow toggleMicrophone()/hangup() rejections (parity with
  SMTC) — avoids an unhandled rejection when clicked mid-teardown.
- App/DesktopChrome: keep wrapper element types stable across the chrome toggle
  (display:contents when off) so flipping it no longer remounts RouterProvider.
- settings: normalizeComposerToolbarOrder also appends missing keys from the
  canonical key set (safety net if a new button is absent from the default order).

Gates: tsc/eslint/prettier clean, build OK, 559 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 10:40:31 -04:00

45 lines
1.9 KiB
TypeScript

import { useEffect } from 'react';
import { useAtomValue } from 'jotai';
import { callEmbedAtom } from '../state/callEmbed';
import { useCallControlState } from '../plugins/call';
import { invokeTauri, useTauriEvent } from './useTauri';
type ThumbbarAction = { action: 'mute' | 'deafen' | 'end' };
/**
* P5-44 — Taskbar thumbnail toolbar (call controls). While a call is active,
* mirrors the mic/sound state onto the native `set_thumbbar` command (three
* Mute / Deafen / End-Call buttons on the Windows taskbar thumbnail toolbar) and
* hides them when the call ends. Thumb-button clicks come back as the
* `thumbbar-action` event and drive the real call controls. No-op in the browser.
*/
export function useTauriThumbbar(): void {
const callEmbed = useAtomValue(callEmbedAtom);
const { microphone, sound } = useCallControlState(callEmbed?.control);
const active = callEmbed !== undefined;
// Muted / deafened only make sense while a call is active; report false
// otherwise so the buttons render in a sane (hidden) state.
const muted = active && !microphone;
const deafened = active && !sound;
useEffect(() => {
invokeTauri('set_thumbbar', { active, muted, deafened });
}, [active, muted, deafened]);
useTauriEvent<ThumbbarAction>('thumbbar-action', ({ action }) => {
if (!callEmbed) return;
if (action === 'mute') {
// toggleMicrophone flips the mic; `microphone === false` means muted.
// Async transport send — swallow rejection (widget mid-teardown), as SMTC does.
callEmbed.control.toggleMicrophone().catch(() => undefined);
} else if (action === 'deafen') {
// toggleSound flips local audio; `sound === false` means deafened. It also
// mutes the mic while deafened, matching the in-app Deafen control.
callEmbed.control.toggleSound();
} else if (action === 'end') {
callEmbed.hangup().catch(() => undefined);
}
});
}