Files
shark/src/routes/downloads/+page.svelte

194 lines
4.3 KiB
Svelte

<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { downloadQueue, clearCompleted, removeFromQueue, type QueueItem } from '$lib/stores/downloadQueue';
import { deezerQueueManager } from '$lib/services/deezer/queueManager';
let queueItems = $state<QueueItem[]>([]);
// Subscribe to queue changes
let unsubscribe: (() => void) | null = null;
onMount(() => {
unsubscribe = downloadQueue.subscribe(state => {
// Convert queue to array ordered by queueOrder
queueItems = state.queueOrder
.map(id => state.queue[id])
.filter(item => item !== undefined);
});
// Start queue processor
deezerQueueManager.start();
});
onDestroy(() => {
if (unsubscribe) {
unsubscribe();
}
});
async function handleClearCompleted() {
await clearCompleted();
}
async function handleRemove(id: string) {
await removeFromQueue(id);
}
function formatProgress(item: QueueItem): string {
if (item.status === 'completed') return 'Done';
if (item.status === 'queued' || item.status === 'paused') return 'Queued';
if (item.status === 'failed') return 'Failed';
return `${Math.round(item.progress)}%`;
}
function getProgressBarStyle(progress: number): string {
return `width: ${progress}%`;
}
</script>
<div class="downloads-page">
<div class="header">
<h2>Downloads</h2>
<div class="header-actions">
<button onclick={handleClearCompleted} disabled={queueItems.every(i => i.status !== 'completed')}>
Clear Completed
</button>
</div>
</div>
{#if queueItems.length === 0}
<div class="empty-state">
<p>No downloads in queue</p>
<p class="help-text">Add tracks, albums, or playlists from services to start downloading</p>
</div>
{:else}
<div class="sunken-panel" style="overflow: auto; flex: 1;">
<table class="interactive">
<thead>
<tr>
<th class="col-title">Title</th>
<th class="col-artist">Artist</th>
<th class="col-progress">Progress</th>
<th class="col-source">Source</th>
<th class="col-actions"></th>
</tr>
</thead>
<tbody>
{#each queueItems as item (item.id)}
<tr>
<td class="col-title">{item.title}</td>
<td class="col-artist">{item.artist}</td>
<td class="col-progress">
{#if item.status === 'downloading'}
<div class="progress-indicator">
<span class="progress-indicator-bar" style={getProgressBarStyle(item.progress)}></span>
</div>
{:else}
{formatProgress(item)}
{/if}
</td>
<td class="col-source">{item.source}</td>
<td class="col-actions">
<button
class="remove-btn"
onclick={() => handleRemove(item.id)}
disabled={item.status === 'downloading'}
title="Remove from queue"
>
</button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{/if}
</div>
<style>
.downloads-page {
display: flex;
flex-direction: column;
padding: 8px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
h2 {
margin: 0;
font-size: 1.4em;
}
.header-actions {
display: flex;
gap: 8px;
}
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: light-dark(#666, #999);
}
.empty-state p {
margin: 8px 0;
}
.help-text {
font-size: 0.9em;
opacity: 0.7;
}
table {
width: 100%;
table-layout: fixed;
}
tbody td {
height: 32px;
}
.col-title {
width: auto;
}
.col-artist {
width: 25%;
}
.col-progress {
width: 20%;
}
.col-source {
width: 10%;
}
.col-actions {
width: 32px;
text-align: center;
}
.remove-btn {
padding: 2px 6px;
font-size: 12px;
min-width: 24px;
height: 20px;
line-height: 1;
}
.remove-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
</style>