feat: add refresh button to collection views

This commit is contained in:
Markury
2026-03-18 11:08:23 -04:00
parent 2c471370e4
commit e5d12c9041
4 changed files with 142 additions and 9 deletions

View File

@@ -13,6 +13,9 @@
onTrackClick?: (index: number) => void;
onDownloadTrack?: (index: number) => void;
onDownloadPlaylist?: () => void;
onRefresh?: () => void;
refreshing?: boolean;
lastCached?: number | null;
downloadingTrackIds?: Set<string>;
}
@@ -27,9 +30,18 @@
onTrackClick,
onDownloadTrack,
onDownloadPlaylist,
onRefresh,
refreshing = false,
lastCached = null,
downloadingTrackIds = new Set()
}: Props = $props();
function formatTimestamp(timestamp: number | null): string {
if (!timestamp) return 'Never';
const date = new Date(timestamp * 1000);
return date.toLocaleString();
}
type ViewMode = 'tracks' | 'info';
let viewMode = $state<ViewMode>('tracks');
@@ -177,14 +189,27 @@
<span class="field-label">Tracks:</span>
<span>{tracks.length}</span>
</div>
{#if lastCached}
<div class="field-row">
<span class="field-label">Last updated:</span>
<span>{formatTimestamp(lastCached)}</span>
</div>
{/if}
</fieldset>
<fieldset style="margin-top: 16px;">
<legend>Actions</legend>
<button onclick={onDownloadPlaylist}>
Download Playlist
</button>
<div class="actions-row">
<div>
<button onclick={onDownloadPlaylist}>Download Playlist</button>
<p class="help-text">Download all tracks and save as m3u8 playlist</p>
</div>
{#if onRefresh}
<button onclick={onRefresh} disabled={refreshing}>
{refreshing ? 'Refreshing...' : 'Refresh Tracks'}
</button>
{/if}
</div>
</fieldset>
</div>
{/if}
@@ -300,6 +325,13 @@
text-align: center;
}
.actions-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
}
.download-btn {
padding: 2px 8px;
font-size: 11px;

View File

@@ -13,6 +13,9 @@
onTrackClick?: (index: number) => void;
onDownloadTrack?: (index: number) => void;
onDownloadPlaylist?: () => void;
onRefresh?: () => void;
refreshing?: boolean;
lastCached?: number | null;
downloadingTrackIds?: Set<string>;
}
@@ -26,9 +29,18 @@
onTrackClick,
onDownloadTrack,
onDownloadPlaylist,
onRefresh,
refreshing = false,
lastCached = null,
downloadingTrackIds = new Set()
}: Props = $props();
function formatTimestamp(timestamp: number | null): string {
if (!timestamp) return 'Never';
const date = new Date(timestamp * 1000);
return date.toLocaleString();
}
type ViewMode = 'tracks' | 'info';
let viewMode = $state<ViewMode>('tracks');
@@ -164,20 +176,40 @@
<span class="field-label">Tracks:</span>
<span>{tracks.length}</span>
</div>
{#if lastCached}
<div class="field-row">
<span class="field-label">Last updated:</span>
<span>{formatTimestamp(lastCached)}</span>
</div>
{/if}
</fieldset>
{#if $deezerAuth.loggedIn}
<fieldset style="margin-top: 16px;">
<legend>Actions</legend>
<button onclick={onDownloadPlaylist}>
Download Playlist
</button>
<div class="actions-row">
<div>
<button onclick={onDownloadPlaylist}>Download Playlist</button>
<p class="help-text">Download all tracks via Deezer and save as m3u8 playlist</p>
</div>
{#if onRefresh}
<button onclick={onRefresh} disabled={refreshing}>
{refreshing ? 'Refreshing...' : 'Refresh Tracks'}
</button>
{/if}
</div>
</fieldset>
{:else}
<fieldset style="margin-top: 16px;">
<legend>Downloads</legend>
<div class="actions-row">
<p class="warning-text">Deezer login required to download Spotify tracks</p>
{#if onRefresh}
<button onclick={onRefresh} disabled={refreshing}>
{refreshing ? 'Refreshing...' : 'Refresh Tracks'}
</button>
{/if}
</div>
<p class="help-text">Sign in to Deezer in Services → Deezer to enable downloads</p>
</fieldset>
{/if}
@@ -306,6 +338,13 @@
color: #808080;
}
.actions-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
}
.warning-text {
margin: 0 0 8px 0;
font-size: 12px;

View File

@@ -352,6 +352,9 @@
onTrackClick={handleTrackClick}
onDownloadTrack={handleDownloadTrack}
onDownloadPlaylist={handleDownloadPlaylist}
onRefresh={refreshPlaylistTracks}
{refreshing}
{lastCached}
{downloadingTrackIds}
/>
{/if}

View File

@@ -8,6 +8,7 @@
getCachedPlaylistTracks,
getCachedTracks,
upsertPlaylistTracks,
upsertTracks,
type SpotifyPlaylist,
type SpotifyPlaylistTrack,
type SpotifyTrack
@@ -22,12 +23,14 @@
let playlistId = $derived($page.params.id!);
let loading = $state(true);
let refreshing = $state(false);
let error = $state<string | null>(null);
let playlist = $state<SpotifyPlaylist | null>(null);
let playlistTracks = $state<SpotifyPlaylistTrack[]>([]);
let selectedTrackIndex = $state<number | null>(null);
let coverImageUrl = $state<string | undefined>(undefined);
let downloadingTrackIds = $state(new Set<string>());
let lastCached = $state<number | null>(null);
// Convert Spotify tracks to Track type for CollectionView
let tracks = $derived<Track[]>(
@@ -96,6 +99,8 @@
cached_at: track.cached_at
}));
lastCached = allTracks[0]?.cached_at || null;
// Set cover art from first track's album
if (allTracks.length > 0) {
if (allTracks[0].album_image_url) {
@@ -171,6 +176,7 @@
} else {
playlist = cachedPlaylist;
coverImageUrl = playlist.image_url;
lastCached = playlist.cached_at;
}
// Load tracks
@@ -204,6 +210,56 @@
}
}
function ensureSpotifyAuth(): boolean {
if (!$spotifyAuth.accessToken || !$spotifyAuth.clientId || !$spotifyAuth.clientSecret || !$spotifyAuth.refreshToken) {
return false;
}
spotifyAPI.setClientCredentials($spotifyAuth.clientId, $spotifyAuth.clientSecret);
spotifyAPI.setTokens($spotifyAuth.accessToken, $spotifyAuth.refreshToken, $spotifyAuth.expiresAt!);
return true;
}
async function refreshPlaylist() {
if (refreshing) return;
if (!ensureSpotifyAuth()) {
setError('Not logged in to Spotify');
return;
}
refreshing = true;
try {
if (playlistId === 'spotify-likes') {
// Refresh liked tracks
const apiTracks = await spotifyAPI.getAllUserTracks();
await upsertTracks(apiTracks);
// Reload from cache
await loadSpotifyLikes();
} else {
// Refresh regular playlist tracks
const apiTracks = await spotifyAPI.getPlaylistTracks(playlistId);
await upsertPlaylistTracks(playlistId, apiTracks);
// Reload from cache
const cachedTracks = await getCachedPlaylistTracks(playlistId);
playlistTracks = cachedTracks;
if (playlist) {
playlist.track_count = cachedTracks.length;
}
}
lastCached = Math.floor(Date.now() / 1000);
console.log('[Spotify Playlist] Refresh complete!');
} catch (e) {
console.error('Error refreshing playlist:', e);
setError('Error refreshing playlist: ' + (e instanceof Error ? e.message : String(e)));
} finally {
refreshing = false;
}
}
function handleTrackClick(index: number) {
selectedTrackIndex = index;
}
@@ -294,6 +350,9 @@
onTrackClick={handleTrackClick}
onDownloadTrack={handleDownloadTrack}
onDownloadPlaylist={handleDownloadPlaylist}
onRefresh={refreshPlaylist}
{refreshing}
{lastCached}
/>
</div>
{/if}