chore(format): run prettier over src/ and scripts/
Formatting was configured but never enforced: `bun run format:check` reported 199 unformatted files and ran in no workflow and in no git hook, so .prettierrc (printWidth 100, trailing commas) described an intention rather than the tree. This is the one-time sweep that makes the check gateable. Whitespace and token-reflow only -- no behavioural change: `bun run check` reports 0 errors and all 1053 frontend tests pass before and after. Kept out of every other commit on purpose. A 199-file diff mixed with real changes is unreviewable, and the next commit turns format:check into a hard CI gate so this cannot silently accumulate again.
This commit is contained in:
@@ -68,18 +68,22 @@
|
||||
title={isConnected
|
||||
? `Casting to ${$selectedSession?.deviceName}`
|
||||
: sessionCount > 0
|
||||
? `Cast to ${sessionCount} available device${sessionCount !== 1 ? 's' : ''}`
|
||||
: 'No devices available'}
|
||||
? `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" />
|
||||
<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" />
|
||||
<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>
|
||||
|
||||
@@ -94,7 +98,9 @@
|
||||
|
||||
<!-- 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>
|
||||
<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>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
const supportsSeek = $derived(playState?.canSeek ?? false);
|
||||
const supportsNextPrevious = $derived(
|
||||
(session.supportedCommands ?? []).includes("NextTrack") &&
|
||||
(session.supportedCommands ?? []).includes("PreviousTrack")
|
||||
(session.supportedCommands ?? []).includes("PreviousTrack"),
|
||||
);
|
||||
|
||||
async function handlePlayPause() {
|
||||
@@ -81,9 +81,9 @@
|
||||
const hours = Math.floor(minutes / 60);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;
|
||||
return `${hours}:${String(minutes % 60).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}`;
|
||||
}
|
||||
return `${minutes}:${String(seconds % 60).padStart(2, '0')}`;
|
||||
return `${minutes}:${String(seconds % 60).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
const positionPercent = $derived(() => {
|
||||
@@ -197,11 +197,15 @@
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-gray-400 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
|
||||
{#if playState.isMuted || (playState.volumeLevel ?? 0) === 0}
|
||||
<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z" />
|
||||
<path
|
||||
d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"
|
||||
/>
|
||||
{:else if (playState.volumeLevel ?? 0) < 50}
|
||||
<path d="M7 9v6h4l5 5V4l-5 5H7z" />
|
||||
{:else}
|
||||
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z" />
|
||||
<path
|
||||
d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
|
||||
@@ -224,7 +228,12 @@
|
||||
{:else}
|
||||
<!-- No media playing -->
|
||||
<div class="flex flex-col items-center justify-center py-12 text-center">
|
||||
<svg class="w-16 h-16 text-gray-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg
|
||||
class="w-16 h-16 text-gray-600 mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
const hours = Math.floor(minutes / 60);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${String(minutes % 60).padStart(2, '0')}:${String(seconds % 60).padStart(2, '0')}`;
|
||||
return `${hours}:${String(minutes % 60).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}`;
|
||||
}
|
||||
return `${minutes}:${String(seconds % 60).padStart(2, '0')}`;
|
||||
return `${minutes}:${String(seconds % 60).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
const playState = $derived(session.playState);
|
||||
@@ -28,11 +28,11 @@
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onclick={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'}"
|
||||
? '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">
|
||||
@@ -76,7 +76,11 @@
|
||||
</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">
|
||||
<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>
|
||||
|
||||
@@ -19,13 +19,15 @@
|
||||
let { isOpen = false, onClose, onSelectSession }: Props = $props();
|
||||
|
||||
// MACs currently part of any sync group, for rendering toggle state.
|
||||
const groupedMacs = $derived(new Set(($lmsSync.groups ?? []).flatMap(
|
||||
(g) => [g.masterMac, ...(g.slaveMacs ?? [])]
|
||||
)));
|
||||
const groupedMacs = $derived(
|
||||
new Set(($lmsSync.groups ?? []).flatMap((g) => [g.masterMac, ...(g.slaveMacs ?? [])])),
|
||||
);
|
||||
|
||||
// The sync master is the LMS zone we're currently controlling. Zones can only
|
||||
// be fused once we have a master to fuse them into.
|
||||
const masterMac = $derived(isLmsSession($selectedSession) ? macForSession($selectedSession) : null);
|
||||
const masterMac = $derived(
|
||||
isLmsSession($selectedSession) ? macForSession($selectedSession) : null,
|
||||
);
|
||||
|
||||
function isZoneFused(session: Session): boolean {
|
||||
const mac = macForSession(session);
|
||||
@@ -111,11 +113,23 @@
|
||||
|
||||
function getSessionIcon(client: string | null | undefined): string {
|
||||
const clientLower = (client ?? "").toLowerCase();
|
||||
if (clientLower.includes("tv") || clientLower.includes("roku") || clientLower.includes("android tv")) {
|
||||
if (
|
||||
clientLower.includes("tv") ||
|
||||
clientLower.includes("roku") ||
|
||||
clientLower.includes("android tv")
|
||||
) {
|
||||
return "tv";
|
||||
} else if (clientLower.includes("web") || clientLower.includes("chrome") || clientLower.includes("firefox")) {
|
||||
} else if (
|
||||
clientLower.includes("web") ||
|
||||
clientLower.includes("chrome") ||
|
||||
clientLower.includes("firefox")
|
||||
) {
|
||||
return "web";
|
||||
} else if (clientLower.includes("mobile") || clientLower.includes("ios") || clientLower.includes("android")) {
|
||||
} else if (
|
||||
clientLower.includes("mobile") ||
|
||||
clientLower.includes("ios") ||
|
||||
clientLower.includes("android")
|
||||
) {
|
||||
return "phone";
|
||||
}
|
||||
return "device";
|
||||
@@ -134,7 +148,9 @@
|
||||
<div
|
||||
class="fixed inset-0 bg-black/60 z-50 flex items-end sm:items-center justify-center p-0 sm:p-4"
|
||||
onclick={handleBackdropClick}
|
||||
onkeydown={(e) => { if (e.key === 'Escape' && onClose) onClose(); }}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === "Escape" && onClose) onClose();
|
||||
}}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="session-picker-title"
|
||||
@@ -148,16 +164,19 @@
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="px-6 py-4 border-b border-gray-800 flex items-center justify-between">
|
||||
<h2 id="session-picker-title" class="text-lg font-semibold text-white">
|
||||
Cast to Device
|
||||
</h2>
|
||||
<h2 id="session-picker-title" class="text-lg font-semibold text-white">Cast to Device</h2>
|
||||
<button
|
||||
onclick={onClose}
|
||||
class="p-2 -m-2 text-gray-400 hover:text-white transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -168,7 +187,9 @@
|
||||
<!-- Loading -->
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<div class="w-8 h-8 border-4 border-[var(--color-jellyfin)] border-t-transparent rounded-full animate-spin"></div>
|
||||
<div
|
||||
class="w-8 h-8 border-4 border-[var(--color-jellyfin)] border-t-transparent rounded-full animate-spin"
|
||||
></div>
|
||||
<p class="text-sm text-gray-400">Searching for devices...</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -177,9 +198,13 @@
|
||||
<div class="p-4 space-y-2">
|
||||
{#if $selectedSession}
|
||||
<!-- Currently connected session -->
|
||||
<div class="mb-4 p-4 rounded-lg bg-[var(--color-jellyfin)]/10 border border-[var(--color-jellyfin)]/30">
|
||||
<div
|
||||
class="mb-4 p-4 rounded-lg bg-[var(--color-jellyfin)]/10 border border-[var(--color-jellyfin)]/30"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-xs font-medium text-[var(--color-jellyfin)] uppercase">Connected</span>
|
||||
<span class="text-xs font-medium text-[var(--color-jellyfin)] uppercase"
|
||||
>Connected</span
|
||||
>
|
||||
<button
|
||||
onclick={handleDisconnect}
|
||||
class="text-xs text-gray-400 hover:text-white transition-colors"
|
||||
@@ -188,16 +213,30 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-[var(--color-jellyfin)]/20 flex items-center justify-center flex-shrink-0">
|
||||
<svg class="w-5 h-5 text-[var(--color-jellyfin)]" fill="currentColor" viewBox="0 0 24 24">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg bg-[var(--color-jellyfin)]/20 flex items-center justify-center flex-shrink-0"
|
||||
>
|
||||
<svg
|
||||
class="w-5 h-5 text-[var(--color-jellyfin)]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
{#if getSessionIcon($selectedSession.client) === "tv"}
|
||||
<path d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z" />
|
||||
<path
|
||||
d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z"
|
||||
/>
|
||||
{:else if getSessionIcon($selectedSession.client) === "web"}
|
||||
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-5 14H4v-4h11v4zm0-5H4V9h11v4zm5 5h-4V9h4v9z" />
|
||||
<path
|
||||
d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-5 14H4v-4h11v4zm0-5H4V9h11v4zm5 5h-4V9h4v9z"
|
||||
/>
|
||||
{:else if getSessionIcon($selectedSession.client) === "phone"}
|
||||
<path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z" />
|
||||
<path
|
||||
d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"
|
||||
/>
|
||||
{:else}
|
||||
<path d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z" />
|
||||
<path
|
||||
d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
</div>
|
||||
@@ -215,28 +254,41 @@
|
||||
{@const lmsZone = isLmsSession(session)}
|
||||
{@const fused = lmsZone && isZoneFused(session)}
|
||||
<button
|
||||
onclick={() => (lmsZone ? handleLmsZoneToggle(session) : handleSessionSelect(session))}
|
||||
onclick={() =>
|
||||
lmsZone ? handleLmsZoneToggle(session) : handleSessionSelect(session)}
|
||||
disabled={$lmsSync.isBusy}
|
||||
aria-pressed={lmsZone ? fused : undefined}
|
||||
class="w-full p-4 rounded-lg border transition-all text-left disabled:opacity-60
|
||||
{fused
|
||||
? 'border-[var(--color-jellyfin)] bg-[var(--color-jellyfin)]/10'
|
||||
: 'border-gray-800 hover:border-[var(--color-jellyfin)]/50 hover:bg-[var(--color-jellyfin)]/5'}"
|
||||
? 'border-[var(--color-jellyfin)] bg-[var(--color-jellyfin)]/10'
|
||||
: 'border-gray-800 hover:border-[var(--color-jellyfin)]/50 hover:bg-[var(--color-jellyfin)]/5'}"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gray-800 flex items-center justify-center flex-shrink-0">
|
||||
<div
|
||||
class="w-10 h-10 rounded-lg bg-gray-800 flex items-center justify-center flex-shrink-0"
|
||||
>
|
||||
<svg class="w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 24 24">
|
||||
{#if lmsZone}
|
||||
<!-- speaker icon for LMS zones -->
|
||||
<path d="M12 2a1 1 0 011 1v18a1 1 0 01-1 1H6a2 2 0 01-2-2V4a2 2 0 012-2h6zm0 2H6v16h6V4zm-3 9a2 2 0 110 4 2 2 0 010-4zm0-2a4 4 0 100 8 4 4 0 000-8zm0-4a1 1 0 110 2 1 1 0 010-2zm9 0h2v2h-2V7zm0 4h2v2h-2v-2z" />
|
||||
<path
|
||||
d="M12 2a1 1 0 011 1v18a1 1 0 01-1 1H6a2 2 0 01-2-2V4a2 2 0 012-2h6zm0 2H6v16h6V4zm-3 9a2 2 0 110 4 2 2 0 010-4zm0-2a4 4 0 100 8 4 4 0 000-8zm0-4a1 1 0 110 2 1 1 0 010-2zm9 0h2v2h-2V7zm0 4h2v2h-2v-2z"
|
||||
/>
|
||||
{:else if getSessionIcon(session.client) === "tv"}
|
||||
<path d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z" />
|
||||
<path
|
||||
d="M21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h5v2h8v-2h5c1.1 0 1.99-.9 1.99-2L23 5c0-1.1-.9-2-2-2zm0 14H3V5h18v12z"
|
||||
/>
|
||||
{:else if getSessionIcon(session.client) === "web"}
|
||||
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-5 14H4v-4h11v4zm0-5H4V9h11v4zm5 5h-4V9h4v9z" />
|
||||
<path
|
||||
d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-5 14H4v-4h11v4zm0-5H4V9h11v4zm5 5h-4V9h4v9z"
|
||||
/>
|
||||
{:else if getSessionIcon(session.client) === "phone"}
|
||||
<path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z" />
|
||||
<path
|
||||
d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"
|
||||
/>
|
||||
{:else}
|
||||
<path d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z" />
|
||||
<path
|
||||
d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
</div>
|
||||
@@ -244,7 +296,11 @@
|
||||
<h3 class="font-medium text-white truncate">{session.deviceName}</h3>
|
||||
<p class="text-sm text-gray-400 truncate">
|
||||
{#if lmsZone}
|
||||
{fused ? "In sync group — tap to remove" : masterMac ? "Tap to add to group" : "Speaker zone"}
|
||||
{fused
|
||||
? "In sync group — tap to remove"
|
||||
: masterMac
|
||||
? "Tap to add to group"
|
||||
: "Speaker zone"}
|
||||
{:else}
|
||||
{session.client}
|
||||
{/if}
|
||||
@@ -259,11 +315,24 @@
|
||||
<!-- Fuse toggle indicator -->
|
||||
<div class="flex-shrink-0">
|
||||
{#if fused}
|
||||
<svg class="w-5 h-5 text-[var(--color-jellyfin)]" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
|
||||
<svg
|
||||
class="w-5 h-5 text-[var(--color-jellyfin)]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<svg class="w-5 h-5 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<svg
|
||||
class="w-5 h-5 text-gray-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="9" stroke-width="2" />
|
||||
</svg>
|
||||
{/if}
|
||||
@@ -271,11 +340,19 @@
|
||||
{:else if session.playState}
|
||||
<div class="flex-shrink-0">
|
||||
{#if session.playState.isPaused}
|
||||
<svg class="w-4 h-4 text-gray-500" fill="currentColor" viewBox="0 0 24 24">
|
||||
<svg
|
||||
class="w-4 h-4 text-gray-500"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
|
||||
</svg>
|
||||
{:else}
|
||||
<svg class="w-4 h-4 text-[var(--color-jellyfin)]" fill="currentColor" viewBox="0 0 24 24">
|
||||
<svg
|
||||
class="w-4 h-4 text-[var(--color-jellyfin)]"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M8 5v14l11-7z" />
|
||||
</svg>
|
||||
{/if}
|
||||
@@ -289,7 +366,12 @@
|
||||
{:else}
|
||||
<!-- Empty state -->
|
||||
<div class="flex flex-col items-center justify-center py-12 px-6 text-center">
|
||||
<svg class="w-16 h-16 text-gray-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg
|
||||
class="w-16 h-16 text-gray-600 mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -322,7 +404,9 @@
|
||||
disabled={$isTransferring}
|
||||
>
|
||||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z" />
|
||||
<path
|
||||
d="M20 18c1.1 0 1.99-.9 1.99-2L22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2H0v2h24v-2h-4zM4 6h16v10H4V6z"
|
||||
/>
|
||||
</svg>
|
||||
Play Locally
|
||||
</button>
|
||||
@@ -354,7 +438,9 @@
|
||||
{#if $isTransferring}
|
||||
<div class="absolute inset-0 bg-black/70 flex items-center justify-center z-10 rounded-2xl">
|
||||
<div class="bg-[var(--color-surface)] p-6 rounded-lg flex flex-col items-center gap-4">
|
||||
<div class="w-10 h-10 border-4 border-[var(--color-jellyfin)] border-t-transparent rounded-full animate-spin"></div>
|
||||
<div
|
||||
class="w-10 h-10 border-4 border-[var(--color-jellyfin)] border-t-transparent rounded-full animate-spin"
|
||||
></div>
|
||||
<p class="text-white font-medium">Transferring playback...</p>
|
||||
<button
|
||||
onclick={() => {
|
||||
@@ -371,10 +457,14 @@
|
||||
|
||||
<!-- LMS sync error -->
|
||||
{#if $lmsSync.error}
|
||||
<div class="absolute bottom-0 left-0 right-0 bg-red-500/90 text-white px-6 py-3 flex items-center justify-between z-10 rounded-b-2xl">
|
||||
<div
|
||||
class="absolute bottom-0 left-0 right-0 bg-red-500/90 text-white px-6 py-3 flex items-center justify-between z-10 rounded-b-2xl"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
|
||||
<path
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm">{$lmsSync.error}</span>
|
||||
</div>
|
||||
@@ -384,7 +474,12 @@
|
||||
aria-label="Dismiss error"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -392,10 +487,14 @@
|
||||
|
||||
<!-- Error Display -->
|
||||
{#if $transferError}
|
||||
<div class="absolute bottom-0 left-0 right-0 bg-red-500/90 text-white px-6 py-3 flex items-center justify-between z-10 rounded-b-2xl">
|
||||
<div
|
||||
class="absolute bottom-0 left-0 right-0 bg-red-500/90 text-white px-6 py-3 flex items-center justify-between z-10 rounded-b-2xl"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
|
||||
<path
|
||||
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-sm">{$transferError}</span>
|
||||
</div>
|
||||
@@ -405,7 +504,12 @@
|
||||
aria-label="Dismiss error"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,9 @@
|
||||
{#if $sessions.isLoading && $sessions.sessions.length === 0}
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<div class="w-8 h-8 border-4 border-[var(--color-jellyfin)] border-t-transparent rounded-full animate-spin"></div>
|
||||
<div
|
||||
class="w-8 h-8 border-4 border-[var(--color-jellyfin)] border-t-transparent rounded-full animate-spin"
|
||||
></div>
|
||||
<p class="text-sm text-gray-400">Loading sessions...</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,7 +77,12 @@
|
||||
{:else if !$sessions.isLoading}
|
||||
<!-- Empty state -->
|
||||
<div class="flex flex-col items-center justify-center py-12 text-center">
|
||||
<svg class="w-16 h-16 text-gray-600 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg
|
||||
class="w-16 h-16 text-gray-600 mb-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
@@ -85,7 +92,8 @@
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-400 mb-2">No Active Sessions</h3>
|
||||
<p class="text-sm text-gray-500 max-w-sm">
|
||||
No controllable Jellyfin sessions found. Start playing media on another device to control it from here.
|
||||
No controllable Jellyfin sessions found. Start playing media on another device to control it
|
||||
from here.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user