Files
jellytau/src/lib/components/sessions/CastButton.svelte
T
dtourolle d01c2aab9f
🏗️ Build and Test JellyTau / Run Tests (pull_request) Successful in 4m39s
Traceability Validation / Check Requirement Traces (pull_request) Failing after 36s
🏗️ Build and Test JellyTau / Build Android APK (pull_request) Failing after 1m57s
Migrate all IPC call sites to typed tauri-specta commands.*
Replace the remaining ~155 untyped invoke() calls across stores, services,
components, and routes with the generated commands.* wrappers from
$lib/api/bindings, so every IPC call is compile-time-checked against the
command signatures.

- Register repository_get_subtitle_url and repository_get_video_download_url
  in specta_builder() and the invoke_handler; regenerate bindings.ts.
- Source duplicated wire types (AutoplaySettings, CacheConfig, Session,
  ConnectivityStatus, audio/video settings, etc.) from bindings.
- Fix two bugs surfaced by the typed wrappers:
  - VideoDownloadButton passed an un-awaited Promise as the stream URL.
  - setAutoplaySettings omitted the required userId argument.
- Update unit tests asserting the old invoke(name, args) shape.
- Remove the five param-naming guard tests; the compiler and codegen now
  enforce what they checked.

svelte-check: 0 errors. vitest: green. cargo test --lib: green.
2026-06-21 08:47:04 +02:00

102 lines
3.2 KiB
Svelte

<!-- TRACES: UR-010 | DR-037 -->
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import { commands } from "$lib/api/bindings";
import { sessions, controllableSessions, selectedSession } from "$lib/stores";
import SessionPickerModal from "./SessionPickerModal.svelte";
interface Props {
size?: "sm" | "md" | "lg";
className?: string;
}
let { size = "md", className = "" }: Props = $props();
let showPicker = $state(false);
// Size classes
const sizeClasses = {
sm: "w-4 h-4",
md: "w-5 h-5",
lg: "w-6 h-6",
};
const buttonSizeClasses = {
sm: "p-1.5",
md: "p-2",
lg: "p-2.5",
};
function handleClick() {
showPicker = true;
}
function closePicker() {
showPicker = false;
}
// Set polling hints when component mounts/unmounts
onMount(async () => {
// Initial manual refresh to get sessions
sessions.refresh();
// Set initial hint based on connection state
const hint = isConnected ? "cast_active" : "cast_discovery";
await commands.sessionsSetPollingHint(hint);
});
onDestroy(async () => {
// Reset to normal polling when component unmounts
await commands.sessionsSetPollingHint("normal");
});
// Update polling hint when connection state changes
$effect(() => {
const hint = isConnected ? "cast_active" : "cast_discovery";
commands.sessionsSetPollingHint(hint);
});
const isConnected = $derived($selectedSession !== null);
const sessionCount = $derived($controllableSessions.length);
</script>
<button
onclick={handleClick}
class="{buttonSizeClasses[size]} {className} rounded-lg transition-colors relative {isConnected
? 'text-[var(--color-jellyfin)] hover:bg-[var(--color-jellyfin)]/10'
: 'text-gray-400 hover:text-white hover:bg-white/10'}"
title={isConnected
? `Casting to ${$selectedSession?.deviceName}`
: sessionCount > 0
? `Cast to ${sessionCount} available device${sessionCount !== 1 ? 's' : ''}`
: 'No devices available'}
aria-label="Cast"
>
<!-- Cast Icon -->
<svg class={sizeClasses[size]} fill="currentColor" viewBox="0 0 24 24">
{#if isConnected}
<!-- Connected cast icon -->
<path d="M1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11zM21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
{:else}
<!-- Standard cast icon -->
<path d="M21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z" />
{/if}
</svg>
<!-- Badge for available sessions count -->
{#if !isConnected && sessionCount > 0}
<span
class="absolute -top-1 -right-1 w-4 h-4 bg-[var(--color-jellyfin)] text-white text-[10px] font-bold rounded-full flex items-center justify-center"
>
{sessionCount}
</span>
{/if}
<!-- Connected indicator -->
{#if isConnected}
<span class="absolute bottom-0 right-0 w-2 h-2 bg-[var(--color-jellyfin)] rounded-full border-2 border-[var(--color-surface)]"></span>
{/if}
</button>
<SessionPickerModal isOpen={showPicker} onClose={closePicker} />