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.
102 lines
3.2 KiB
Svelte
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} />
|