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,114 @@
<script lang="ts">
import type { Session } from "$lib/api/types";
import { auth } from "$lib/stores/auth";
interface Props {
session: Session;
selected?: boolean;
onclick?: () => void;
}
let { session, selected = false, onclick }: Props = $props();
function getImageUrl(): string {
if (!session.nowPlayingItem) return "";
try {
const repo = auth.getRepository();
return repo.getImageUrl(session.nowPlayingItem.id, "Primary", {
maxWidth: 80,
tag: session.nowPlayingItem.primaryImageTag,
});
} catch {
return "";
}
}
function formatTime(ticks: number): string {
const seconds = Math.floor(ticks / 10000000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}:${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;
}
return `${minutes}:${String(seconds % 60).padStart(2, '0')}`;
}
const imageUrl = $derived(getImageUrl());
const playState = $derived(session.playState);
const nowPlaying = $derived(session.nowPlayingItem);
</script>
<button
type="button"
onclick={onclick}
class="w-full p-4 rounded-lg border-2 transition-all text-left
{selected
? 'border-[var(--color-jellyfin)] bg-[var(--color-jellyfin)]/10'
: 'border-[var(--color-surface)] bg-[var(--color-surface)] hover:border-[var(--color-jellyfin)]/50'}"
>
<!-- Session header -->
<div class="flex items-start gap-3 mb-2">
<div class="flex-1 min-w-0">
<h3 class="font-semibold text-white truncate">{session.deviceName}</h3>
<p class="text-sm text-gray-400 truncate">{session.client}{session.userName}</p>
</div>
{#if selected}
<div class="flex-shrink-0 w-2 h-2 rounded-full bg-[var(--color-jellyfin)]"></div>
{/if}
</div>
<!-- Now playing -->
{#if nowPlaying && playState}
<div class="flex items-center gap-3 mt-3 pt-3 border-t border-white/10">
{#if imageUrl}
<img
src={imageUrl}
alt={nowPlaying.name}
class="w-12 h-12 rounded object-cover flex-shrink-0"
/>
{/if}
<div class="flex-1 min-w-0">
<p class="text-sm font-medium text-white truncate">{nowPlaying.name}</p>
{#if nowPlaying.artists && nowPlaying.artists.length > 0}
<p class="text-xs text-gray-400 truncate">{nowPlaying.artists.join(", ")}</p>
{:else if nowPlaying.albumName}
<p class="text-xs text-gray-400 truncate">{nowPlaying.albumName}</p>
{/if}
<div class="flex items-center gap-2 mt-1">
<div class="flex items-center gap-1">
{#if playState.isPaused}
<svg class="w-3 h-3 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
</svg>
<span class="text-xs text-gray-400">Paused</span>
{:else}
<svg class="w-3 h-3 text-[var(--color-jellyfin)]" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z" />
</svg>
<span class="text-xs text-[var(--color-jellyfin)]">Playing</span>
{/if}
</div>
{#if playState.positionTicks}
<span class="text-xs text-gray-500"></span>
<span class="text-xs text-gray-400">{formatTime(playState.positionTicks)}</span>
{/if}
{#if playState.volumeLevel !== undefined}
<span class="text-xs text-gray-500"></span>
<span class="text-xs text-gray-400">Vol {playState.volumeLevel}%</span>
{/if}
</div>
</div>
</div>
{:else}
<div class="mt-3 pt-3 border-t border-white/10">
<p class="text-sm text-gray-500 italic">No media playing</p>
</div>
{/if}
</button>