refactor: Prototype of tanstack-query (#34)
* move getApiUrl to api folder * adjust imports * tanstack-query example with homeData * small adjustments * fix key collision * new MusicSource persistent mechanism example * add error handling & set sveltekit to SPA mode * remove unnecessary ssr test
This commit is contained in:
+330
-327
@@ -51,6 +51,7 @@
|
||||
ListMusic
|
||||
} from 'lucide-svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import QueryProvider from '$lib/queries/QueryProvider.svelte';
|
||||
|
||||
let { children }: { children: Snippet } = $props();
|
||||
|
||||
@@ -234,344 +235,346 @@
|
||||
const lidarrConfigured = $derived(integrations.current.lidarr || !integrations.current.loaded);
|
||||
</script>
|
||||
|
||||
<div data-theme="musicseerr">
|
||||
{#if showNavigationProgress}
|
||||
<div class="fixed top-0 left-0 right-0 z-120 pointer-events-none">
|
||||
<progress class="progress progress-primary w-full h-1"></progress>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<DegradedBanner />
|
||||
|
||||
<div class="drawer drawer-open">
|
||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
||||
|
||||
<div class="drawer-content flex flex-col">
|
||||
<div class="navbar bg-base-100 shadow-sm sticky top-0 z-50">
|
||||
<div class="navbar-start w-auto">
|
||||
<a href="/" class="btn btn-ghost" aria-label="Home">
|
||||
<img src="/logo_wide.png" alt="Musicseerr" class="h-8" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-center grow px-4 justify-center">
|
||||
<div class="w-full max-w-2xl">
|
||||
<SearchSuggestions
|
||||
bind:query
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleSuggestionSelect}
|
||||
id="navbar-suggest"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-end w-auto pr-2">
|
||||
<a href="/profile" class="btn btn-ghost btn-circle btn-md" aria-label="Profile">
|
||||
<UserRound class="h-6 w-6" />
|
||||
</a>
|
||||
</div>
|
||||
<QueryProvider>
|
||||
<div data-theme="musicseerr">
|
||||
{#if showNavigationProgress}
|
||||
<div class="fixed top-0 left-0 right-0 z-120 pointer-events-none">
|
||||
<progress class="progress progress-primary w-full h-1"></progress>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex-1" class:pb-24={playerStore.isPlayerVisible}>
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
<DegradedBanner />
|
||||
|
||||
<div class="drawer-side is-drawer-close:overflow-visible">
|
||||
<label for="main-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<div
|
||||
class="is-drawer-close:w-16 is-drawer-open:w-64 bg-base-200 flex flex-col items-start min-h-full"
|
||||
>
|
||||
<ul class="menu w-full grow p-2 [&_li>*]:py-3">
|
||||
<li>
|
||||
<button
|
||||
onclick={() =>
|
||||
(document.getElementById('search_modal') as HTMLDialogElement)?.showModal()}
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Search"
|
||||
>
|
||||
<Search class="h-6 w-6" />
|
||||
<span class="is-drawer-close:hidden">Search</span>
|
||||
</button>
|
||||
</li>
|
||||
<div class="drawer drawer-open">
|
||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" />
|
||||
|
||||
<div class="divider my-0"></div>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="/"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Home"
|
||||
>
|
||||
<House class="h-6 w-6" />
|
||||
<span class="is-drawer-close:hidden">Home</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="/discover"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Discover"
|
||||
>
|
||||
<Compass class="h-6 w-6" />
|
||||
<span class="is-drawer-close:hidden">Discover</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{#if lidarrConfigured}
|
||||
<li>
|
||||
<a
|
||||
href="/library"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Library"
|
||||
>
|
||||
<div class="relative">
|
||||
<Menu class="h-6 w-6" />
|
||||
{#if syncStatus.isActive}
|
||||
<span
|
||||
class="absolute -top-1 -right-1 badge badge-primary badge-xs w-2.5 h-2.5 p-0 animate-pulse"
|
||||
aria-label="Library sync in progress"
|
||||
></span>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="is-drawer-close:hidden">Library</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="/playlists"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
class:menu-active={isNavActive('/playlists')}
|
||||
aria-current={isNavActive('/playlists') ? 'page' : undefined}
|
||||
data-tip="Playlists"
|
||||
>
|
||||
<ListMusic class="h-6 w-6" />
|
||||
<span class="is-drawer-close:hidden">Playlists</span>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.loaded}
|
||||
<div class="divider my-0"></div>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.youtube}
|
||||
<li>
|
||||
<a
|
||||
href="/library/youtube"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="YouTube"
|
||||
>
|
||||
<YouTubeIcon class="h-6 w-6 text-error" />
|
||||
<span class="is-drawer-close:hidden">YouTube</span>
|
||||
</a>
|
||||
</li>
|
||||
{:else if integrations.current.loaded}
|
||||
<SidebarServiceHint label="YouTube" settingsTab="youtube">
|
||||
{#snippet icon()}<YouTubeIcon class="h-6 w-6 text-error" />{/snippet}
|
||||
</SidebarServiceHint>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.jellyfin}
|
||||
<li>
|
||||
<a
|
||||
href="/library/jellyfin"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Jellyfin"
|
||||
>
|
||||
<JellyfinIcon class="h-6 w-6 text-info" />
|
||||
<span class="is-drawer-close:hidden">Jellyfin</span>
|
||||
</a>
|
||||
</li>
|
||||
{:else if integrations.current.loaded}
|
||||
<SidebarServiceHint label="Jellyfin" settingsTab="jellyfin">
|
||||
{#snippet icon()}<JellyfinIcon class="h-6 w-6 text-info" />{/snippet}
|
||||
</SidebarServiceHint>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.navidrome}
|
||||
<li>
|
||||
<a
|
||||
href="/library/navidrome"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Navidrome"
|
||||
>
|
||||
<NavidromeIcon class="h-6 w-6 text-primary" />
|
||||
<span class="is-drawer-close:hidden">Navidrome</span>
|
||||
</a>
|
||||
</li>
|
||||
{:else if integrations.current.loaded}
|
||||
<SidebarServiceHint label="Navidrome" settingsTab="navidrome">
|
||||
{#snippet icon()}<NavidromeIcon class="h-6 w-6 text-primary" />{/snippet}
|
||||
</SidebarServiceHint>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.localfiles}
|
||||
<li>
|
||||
<a
|
||||
href="/library/local"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Local Files"
|
||||
>
|
||||
<Headphones class="h-6 w-6 text-accent" />
|
||||
<span class="is-drawer-close:hidden">Local Files</span>
|
||||
</a>
|
||||
</li>
|
||||
{:else if integrations.current.loaded}
|
||||
<SidebarServiceHint label="Local Files" settingsTab="local-files">
|
||||
{#snippet icon()}<Headphones class="h-6 w-6 text-accent" />{/snippet}
|
||||
</SidebarServiceHint>
|
||||
{/if}
|
||||
|
||||
{#if lidarrConfigured}
|
||||
<div class="divider my-0"></div>
|
||||
<li>
|
||||
<a
|
||||
href="/requests"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Requests"
|
||||
>
|
||||
<div class="relative">
|
||||
<Download class="h-6 w-6" />
|
||||
{#if requestCountStore.count > 0}
|
||||
<span
|
||||
class="absolute -top-2 -right-2 badge badge-info badge-xs w-4 h-4 p-0 text-[10px] font-bold"
|
||||
>{requestCountStore.count}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="is-drawer-close:hidden">Requests</span>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
<div class="w-full p-2 flex flex-col gap-1" class:pb-24={playerStore.isPlayerVisible}>
|
||||
<div class="is-drawer-close:tooltip is-drawer-close:tooltip-right" data-tip="Settings">
|
||||
<a href="/settings" class="btn btn-ghost btn-circle" aria-label="Settings">
|
||||
<Settings class="h-6 w-6" />
|
||||
<div class="drawer-content flex flex-col">
|
||||
<div class="navbar bg-base-100 shadow-sm sticky top-0 z-50">
|
||||
<div class="navbar-start w-auto">
|
||||
<a href="/" class="btn btn-ghost" aria-label="Home">
|
||||
<img src="/logo_wide.png" alt="Musicseerr" class="h-8" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="is-drawer-close:tooltip is-drawer-close:tooltip-right" data-tip="Open">
|
||||
<label
|
||||
for="main-drawer"
|
||||
class="btn btn-ghost btn-circle drawer-button is-drawer-open:rotate-y-180"
|
||||
>
|
||||
<PanelLeft class="h-6 w-6" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog id="search_modal" class="modal">
|
||||
<div class="modal-box overflow-visible">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" aria-label="Close"
|
||||
><X class="h-4 w-4" /></button
|
||||
>
|
||||
</form>
|
||||
<h3 class="font-bold text-lg mb-4">Search</h3>
|
||||
<SearchSuggestions
|
||||
bind:query={modalQuery}
|
||||
onSearch={handleModalSearch}
|
||||
onSelect={handleModalSuggestionSelect}
|
||||
placeholder="Search albums or artists..."
|
||||
autofocus={true}
|
||||
id="modal-suggest"
|
||||
/>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button aria-label="Close modal">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
{#if $errorModal.show}
|
||||
<dialog class="modal modal-open">
|
||||
<div class="modal-box bg-base-200 border border-base-300 shadow-xl max-w-md">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute right-3 top-3 opacity-60 hover:opacity-100"
|
||||
onclick={() => errorModal.hide()}
|
||||
aria-label="Close"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div class="flex flex-col items-center text-center pt-2 pb-1">
|
||||
<div class="bg-error/10 rounded-full p-3 mb-4">
|
||||
<TriangleAlert class="h-8 w-8 text-error" />
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-bold text-base-content mb-2">
|
||||
{$errorModal.title}
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-base-content/70 leading-relaxed">
|
||||
{$errorModal.message}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if $errorModal.details}
|
||||
<div class="mt-4 rounded-box bg-base-300/60 border border-base-300 p-4">
|
||||
<div class="flex gap-3 items-start">
|
||||
<Info class="h-5 w-5 text-info shrink-0 mt-0.5" />
|
||||
<p class="text-sm text-base-content/80 leading-relaxed text-left">
|
||||
{$errorModal.details}
|
||||
</p>
|
||||
<div class="navbar-center grow px-4 justify-center">
|
||||
<div class="w-full max-w-2xl">
|
||||
<SearchSuggestions
|
||||
bind:query
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleSuggestionSelect}
|
||||
id="navbar-suggest"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="navbar-end w-auto pr-2">
|
||||
<a href="/profile" class="btn btn-ghost btn-circle btn-md" aria-label="Profile">
|
||||
<UserRound class="h-6 w-6" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-action justify-center mt-5">
|
||||
<button class="btn btn-accent btn-sm px-6" onclick={() => errorModal.hide()}>
|
||||
Dismiss
|
||||
<div class="flex-1" class:pb-24={playerStore.isPlayerVisible}>
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-side is-drawer-close:overflow-visible">
|
||||
<label for="main-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||
<div
|
||||
class="is-drawer-close:w-16 is-drawer-open:w-64 bg-base-200 flex flex-col items-start min-h-full"
|
||||
>
|
||||
<ul class="menu w-full grow p-2 [&_li>*]:py-3">
|
||||
<li>
|
||||
<button
|
||||
onclick={() =>
|
||||
(document.getElementById('search_modal') as HTMLDialogElement)?.showModal()}
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Search"
|
||||
>
|
||||
<Search class="h-6 w-6" />
|
||||
<span class="is-drawer-close:hidden">Search</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<div class="divider my-0"></div>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="/"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Home"
|
||||
>
|
||||
<House class="h-6 w-6" />
|
||||
<span class="is-drawer-close:hidden">Home</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="/discover"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Discover"
|
||||
>
|
||||
<Compass class="h-6 w-6" />
|
||||
<span class="is-drawer-close:hidden">Discover</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{#if lidarrConfigured}
|
||||
<li>
|
||||
<a
|
||||
href="/library"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Library"
|
||||
>
|
||||
<div class="relative">
|
||||
<Menu class="h-6 w-6" />
|
||||
{#if syncStatus.isActive}
|
||||
<span
|
||||
class="absolute -top-1 -right-1 badge badge-primary badge-xs w-2.5 h-2.5 p-0 animate-pulse"
|
||||
aria-label="Library sync in progress"
|
||||
></span>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="is-drawer-close:hidden">Library</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a
|
||||
href="/playlists"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
class:menu-active={isNavActive('/playlists')}
|
||||
aria-current={isNavActive('/playlists') ? 'page' : undefined}
|
||||
data-tip="Playlists"
|
||||
>
|
||||
<ListMusic class="h-6 w-6" />
|
||||
<span class="is-drawer-close:hidden">Playlists</span>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.loaded}
|
||||
<div class="divider my-0"></div>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.youtube}
|
||||
<li>
|
||||
<a
|
||||
href="/library/youtube"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="YouTube"
|
||||
>
|
||||
<YouTubeIcon class="h-6 w-6 text-error" />
|
||||
<span class="is-drawer-close:hidden">YouTube</span>
|
||||
</a>
|
||||
</li>
|
||||
{:else if integrations.current.loaded}
|
||||
<SidebarServiceHint label="YouTube" settingsTab="youtube">
|
||||
{#snippet icon()}<YouTubeIcon class="h-6 w-6 text-error" />{/snippet}
|
||||
</SidebarServiceHint>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.jellyfin}
|
||||
<li>
|
||||
<a
|
||||
href="/library/jellyfin"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Jellyfin"
|
||||
>
|
||||
<JellyfinIcon class="h-6 w-6 text-info" />
|
||||
<span class="is-drawer-close:hidden">Jellyfin</span>
|
||||
</a>
|
||||
</li>
|
||||
{:else if integrations.current.loaded}
|
||||
<SidebarServiceHint label="Jellyfin" settingsTab="jellyfin">
|
||||
{#snippet icon()}<JellyfinIcon class="h-6 w-6 text-info" />{/snippet}
|
||||
</SidebarServiceHint>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.navidrome}
|
||||
<li>
|
||||
<a
|
||||
href="/library/navidrome"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Navidrome"
|
||||
>
|
||||
<NavidromeIcon class="h-6 w-6 text-primary" />
|
||||
<span class="is-drawer-close:hidden">Navidrome</span>
|
||||
</a>
|
||||
</li>
|
||||
{:else if integrations.current.loaded}
|
||||
<SidebarServiceHint label="Navidrome" settingsTab="navidrome">
|
||||
{#snippet icon()}<NavidromeIcon class="h-6 w-6 text-primary" />{/snippet}
|
||||
</SidebarServiceHint>
|
||||
{/if}
|
||||
|
||||
{#if integrations.current.localfiles}
|
||||
<li>
|
||||
<a
|
||||
href="/library/local"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Local Files"
|
||||
>
|
||||
<Headphones class="h-6 w-6 text-accent" />
|
||||
<span class="is-drawer-close:hidden">Local Files</span>
|
||||
</a>
|
||||
</li>
|
||||
{:else if integrations.current.loaded}
|
||||
<SidebarServiceHint label="Local Files" settingsTab="local-files">
|
||||
{#snippet icon()}<Headphones class="h-6 w-6 text-accent" />{/snippet}
|
||||
</SidebarServiceHint>
|
||||
{/if}
|
||||
|
||||
{#if lidarrConfigured}
|
||||
<div class="divider my-0"></div>
|
||||
<li>
|
||||
<a
|
||||
href="/requests"
|
||||
class="is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
data-tip="Requests"
|
||||
>
|
||||
<div class="relative">
|
||||
<Download class="h-6 w-6" />
|
||||
{#if requestCountStore.count > 0}
|
||||
<span
|
||||
class="absolute -top-2 -right-2 badge badge-info badge-xs w-4 h-4 p-0 text-[10px] font-bold"
|
||||
>{requestCountStore.count}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="is-drawer-close:hidden">Requests</span>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
<div class="w-full p-2 flex flex-col gap-1" class:pb-24={playerStore.isPlayerVisible}>
|
||||
<div class="is-drawer-close:tooltip is-drawer-close:tooltip-right" data-tip="Settings">
|
||||
<a href="/settings" class="btn btn-ghost btn-circle" aria-label="Settings">
|
||||
<Settings class="h-6 w-6" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="is-drawer-close:tooltip is-drawer-close:tooltip-right" data-tip="Open">
|
||||
<label
|
||||
for="main-drawer"
|
||||
class="btn btn-ghost btn-circle drawer-button is-drawer-open:rotate-y-180"
|
||||
>
|
||||
<PanelLeft class="h-6 w-6" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog id="search_modal" class="modal">
|
||||
<div class="modal-box overflow-visible">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" aria-label="Close"
|
||||
><X class="h-4 w-4" /></button
|
||||
>
|
||||
</form>
|
||||
<h3 class="font-bold text-lg mb-4">Search</h3>
|
||||
<SearchSuggestions
|
||||
bind:query={modalQuery}
|
||||
onSearch={handleModalSearch}
|
||||
onSelect={handleModalSuggestionSelect}
|
||||
placeholder="Search albums or artists..."
|
||||
autofocus={true}
|
||||
id="modal-suggest"
|
||||
/>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button aria-label="Close modal">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
{#if $errorModal.show}
|
||||
<dialog class="modal modal-open">
|
||||
<div class="modal-box bg-base-200 border border-base-300 shadow-xl max-w-md">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute right-3 top-3 opacity-60 hover:opacity-100"
|
||||
onclick={() => errorModal.hide()}
|
||||
aria-label="Close"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<div class="flex flex-col items-center text-center pt-2 pb-1">
|
||||
<div class="bg-error/10 rounded-full p-3 mb-4">
|
||||
<TriangleAlert class="h-8 w-8 text-error" />
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-bold text-base-content mb-2">
|
||||
{$errorModal.title}
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-base-content/70 leading-relaxed">
|
||||
{$errorModal.message}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if $errorModal.details}
|
||||
<div class="mt-4 rounded-box bg-base-300/60 border border-base-300 p-4">
|
||||
<div class="flex gap-3 items-start">
|
||||
<Info class="h-5 w-5 text-info shrink-0 mt-0.5" />
|
||||
<p class="text-sm text-base-content/80 leading-relaxed text-left">
|
||||
{$errorModal.details}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="modal-action justify-center mt-5">
|
||||
<button class="btn btn-accent btn-sm px-6" onclick={() => errorModal.hide()}>
|
||||
Dismiss
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<form method="dialog" class="modal-backdrop" onclick={() => errorModal.hide()}>
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
{/if}
|
||||
|
||||
{#if playbackToast.visible}
|
||||
<div
|
||||
class="fixed z-50 left-1/2 -translate-x-1/2 transition-all duration-300"
|
||||
style="bottom: {playerStore.isPlayerVisible ? '100px' : '16px'}"
|
||||
>
|
||||
<div
|
||||
class="alert {playbackToast.type === 'error'
|
||||
? 'alert-error'
|
||||
: playbackToast.type === 'warning'
|
||||
? 'alert-warning'
|
||||
: 'alert-info'} shadow-lg px-4 py-2 min-w-64 max-w-md"
|
||||
>
|
||||
{#if playbackToast.type === 'error'}
|
||||
<X class="h-5 w-5 shrink-0" />
|
||||
{:else if playbackToast.type === 'warning'}
|
||||
<TriangleAlert class="h-5 w-5 shrink-0" />
|
||||
{:else}
|
||||
<Info class="h-5 w-5 shrink-0" />
|
||||
{/if}
|
||||
<span class="text-sm">{playbackToast.message}</span>
|
||||
<button
|
||||
class="btn btn-ghost btn-xs btn-circle"
|
||||
onclick={() => playbackToast.dismiss()}
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<form method="dialog" class="modal-backdrop" onclick={() => errorModal.hide()}>
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if playbackToast.visible}
|
||||
<div
|
||||
class="fixed z-50 left-1/2 -translate-x-1/2 transition-all duration-300"
|
||||
style="bottom: {playerStore.isPlayerVisible ? '100px' : '16px'}"
|
||||
>
|
||||
<div
|
||||
class="alert {playbackToast.type === 'error'
|
||||
? 'alert-error'
|
||||
: playbackToast.type === 'warning'
|
||||
? 'alert-warning'
|
||||
: 'alert-info'} shadow-lg px-4 py-2 min-w-64 max-w-md"
|
||||
>
|
||||
{#if playbackToast.type === 'error'}
|
||||
<X class="h-5 w-5 shrink-0" />
|
||||
{:else if playbackToast.type === 'warning'}
|
||||
<TriangleAlert class="h-5 w-5 shrink-0" />
|
||||
{:else}
|
||||
<Info class="h-5 w-5 shrink-0" />
|
||||
{/if}
|
||||
<span class="text-sm">{playbackToast.message}</span>
|
||||
<button
|
||||
class="btn btn-ghost btn-xs btn-circle"
|
||||
onclick={() => playbackToast.dismiss()}
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if browser}
|
||||
<audio bind:this={audioElement}></audio>
|
||||
{/if}
|
||||
|
||||
{#if browser}
|
||||
<audio bind:this={audioElement}></audio>
|
||||
{/if}
|
||||
|
||||
<Player />
|
||||
<CacheSyncIndicator />
|
||||
<AddToPlaylistModal bind:this={playlistModalRef} />
|
||||
</div>
|
||||
<Player />
|
||||
<CacheSyncIndicator />
|
||||
<AddToPlaylistModal bind:this={playlistModalRef} />
|
||||
</div>
|
||||
</QueryProvider>
|
||||
|
||||
Reference in New Issue
Block a user