First working POC

This commit is contained in:
2026-01-26 22:21:54 +01:00
commit cfddc1edea
255 changed files with 77606 additions and 0 deletions
@@ -0,0 +1,100 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import { invoke } from "@tauri-apps/api/core";
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 invoke("sessions_set_polling_hint", { hint });
});
onDestroy(async () => {
// Reset to normal polling when component unmounts
await invoke("sessions_set_polling_hint", { hint: "normal" });
});
// Update polling hint when connection state changes
$effect(() => {
const hint = isConnected ? "cast_active" : "cast_discovery";
invoke("sessions_set_polling_hint", { 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} />