diff --git a/web-player/src/lib/actions/onEscape.ts b/web-player/src/lib/actions/onEscape.ts new file mode 100644 index 0000000..368863d --- /dev/null +++ b/web-player/src/lib/actions/onEscape.ts @@ -0,0 +1,17 @@ +import type { Action } from 'svelte/action'; + +export const onEscape: Action void) | undefined> = (_node, handler) => { + function onKeydown(event: KeyboardEvent) { + if (event.key === 'Escape') handler?.(); + } + window.addEventListener('keydown', onKeydown); + + return { + update(next) { + handler = next; + }, + destroy() { + window.removeEventListener('keydown', onKeydown); + } + }; +}; diff --git a/web-player/src/lib/components/layout/MobileHeader.svelte b/web-player/src/lib/components/layout/MobileHeader.svelte index 98c3062..7c32a0d 100644 --- a/web-player/src/lib/components/layout/MobileHeader.svelte +++ b/web-player/src/lib/components/layout/MobileHeader.svelte @@ -5,7 +5,6 @@ import LogIn from '@lucide/svelte/icons/log-in'; import MenuItem from '../ui/overlay/MenuItem.svelte'; import AccountMenu from '../ui/overlay/AccountMenu.svelte'; - import Avatar from '../ui/media/Avatar.svelte'; import IconButton from '../ui/primitives/IconButton.svelte'; import Logo from '../ui/primitives/Logo.svelte'; @@ -26,17 +25,6 @@ {#if user} - {#snippet trigger({ toggle, open })} - - {/snippet} {#snippet extraItems({ close })} diff --git a/web-player/src/lib/components/layout/TopBar.svelte b/web-player/src/lib/components/layout/TopBar.svelte index 416f6ef..6c3d432 100644 --- a/web-player/src/lib/components/layout/TopBar.svelte +++ b/web-player/src/lib/components/layout/TopBar.svelte @@ -8,7 +8,6 @@ import X from '@lucide/svelte/icons/x'; import ChevronLeft from '@lucide/svelte/icons/chevron-left'; import AccountMenu from '../ui/overlay/AccountMenu.svelte'; - import Avatar from '../ui/media/Avatar.svelte'; import IconButton from '../ui/primitives/IconButton.svelte'; import Button from '../ui/primitives/Button.svelte'; @@ -86,24 +85,7 @@
{#if user} - - {#snippet trigger({ toggle, open })} - - {/snippet} - + {:else} { - src; - srcFailed = false; - }); + let failedSrc = $state(null); + let failedSingle = $state(null); - let singleFailed = $state(false); - $effect(() => { - trackIds[0]; - singleFailed = false; - }); - - const showSrc = $derived(!!src && !srcFailed); + const showSrc = $derived(!!src && failedSrc !== src); const showMosaic = $derived(!showSrc && trackIds.length >= 4); const singleId = $derived(!showSrc && !showMosaic && trackIds.length > 0 ? trackIds[0] : null); @@ -87,7 +78,7 @@ loading="lazy" decoding="async" fetchpriority="low" - onerror={() => (srcFailed = true)} + onerror={() => (failedSrc = src ?? null)} class="h-full w-full object-cover" /> {:else if showMosaic} @@ -104,14 +95,14 @@ /> {/each}
- {:else if singleId !== null && !singleFailed} + {:else if singleId !== null && failedSingle !== singleId} (singleFailed = true)} + onerror={() => (failedSingle = singleId)} class="h-full w-full object-cover" /> {:else} diff --git a/web-player/src/lib/components/ui/media/Avatar.svelte b/web-player/src/lib/components/ui/media/Avatar.svelte index d26b0b5..678daef 100644 --- a/web-player/src/lib/components/ui/media/Avatar.svelte +++ b/web-player/src/lib/components/ui/media/Avatar.svelte @@ -33,11 +33,7 @@ .join('') ); - let failed = $state(false); - $effect(() => { - src; - failed = false; - }); + let failedSrc = $state(null);
- {#if src && !failed} + {#if src && failedSrc !== src} (failed = true)} + onerror={() => (failedSrc = src ?? null)} class="absolute inset-0 h-full w-full object-cover" /> {:else} diff --git a/web-player/src/lib/components/ui/overlay/AccountMenu.svelte b/web-player/src/lib/components/ui/overlay/AccountMenu.svelte index dac3ebb..3e28122 100644 --- a/web-player/src/lib/components/ui/overlay/AccountMenu.svelte +++ b/web-player/src/lib/components/ui/overlay/AccountMenu.svelte @@ -10,16 +10,17 @@ import GlassMenu from './GlassMenu.svelte'; import { themeMode } from '$lib/theme/mode.svelte'; import { clickOutside } from '$lib/actions/clickOutside'; + import { onEscape } from '$lib/actions/onEscape'; + import Avatar from '../media/Avatar.svelte'; interface Props { user: SessionUser; accountUrl?: string | null; width?: 'sm' | 'md'; extraItems?: Snippet<[{ close: () => void }]>; - trigger: Snippet<[{ toggle: () => void; open: boolean }]>; } - let { user, accountUrl, width = 'sm', extraItems, trigger }: Props = $props(); + let { user, accountUrl, width = 'sm', extraItems }: Props = $props(); const WIDTH: Record<'sm' | 'md', string> = { sm: 'w-52', md: 'w-56' }; @@ -32,16 +33,23 @@ function close() { open = false; } - - function onWindowKeydown(event: KeyboardEvent) { - if (event.key === 'Escape') close(); - } - - -
- {@render trigger({ toggle, open })} +
+ {#if open} import { enhance } from '$app/forms'; + import { onEscape } from '$lib/actions/onEscape'; import { playlistCover } from '$lib/utils/hrefs'; import type { LucideIcon } from '@lucide/svelte'; import ChevronRight from '@lucide/svelte/icons/chevron-right'; @@ -18,17 +19,12 @@ } let { x, y, openLeft, onClose, items, playlistAction, playlists = [] }: Props = $props(); - - function onWindowKeydown(event: KeyboardEvent) { - if (event.key === 'Escape') onClose(); - } - -