mirror of
https://github.com/markuryy/shark.git
synced 2026-06-18 18:41:03 +00:00
feat: add refresh button to collection views
This commit is contained in:
@@ -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>
|
||||
<p class="help-text">Download all tracks and save as m3u8 playlist</p>
|
||||
<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;
|
||||
|
||||
@@ -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>
|
||||
<p class="help-text">Download all tracks via Deezer and save as m3u8 playlist</p>
|
||||
<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>
|
||||
<p class="warning-text">Deezer login required to download Spotify tracks</p>
|
||||
<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;
|
||||
|
||||
@@ -352,6 +352,9 @@
|
||||
onTrackClick={handleTrackClick}
|
||||
onDownloadTrack={handleDownloadTrack}
|
||||
onDownloadPlaylist={handleDownloadPlaylist}
|
||||
onRefresh={refreshPlaylistTracks}
|
||||
{refreshing}
|
||||
{lastCached}
|
||||
{downloadingTrackIds}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user