Design Prot Merge #1
@@ -45,6 +45,10 @@ export function queueIdForTrack(track: ApiTrackLike): string | number {
|
|||||||
return isPendingYouTubeTrack(track) && track.externalId ? track.externalId : track.id;
|
return isPendingYouTubeTrack(track) && track.externalId ? track.externalId : track.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function youTubeThumbnailUrl(videoId: string): string {
|
||||||
|
return `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`;
|
||||||
|
}
|
||||||
|
|
||||||
export function toQueueItems(tracks: ApiTrackLike[]): QueueItem[] {
|
export function toQueueItems(tracks: ApiTrackLike[]): QueueItem[] {
|
||||||
return tracks.map((track) => {
|
return tracks.map((track) => {
|
||||||
const pendingYouTube = isPendingYouTubeTrack(track);
|
const pendingYouTube = isPendingYouTubeTrack(track);
|
||||||
@@ -53,7 +57,7 @@ export function toQueueItems(tracks: ApiTrackLike[]): QueueItem[] {
|
|||||||
title: track.title,
|
title: track.title,
|
||||||
artist: track.artist,
|
artist: track.artist,
|
||||||
source: pendingYouTube ? 'youtube' : 'local',
|
source: pendingYouTube ? 'youtube' : 'local',
|
||||||
coverUrl: pendingYouTube ? `/api/tracks/${track.id}/cover?size=small` : undefined
|
coverUrl: pendingYouTube && track.externalId ? youTubeThumbnailUrl(track.externalId) : undefined
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
player,
|
player,
|
||||||
isPendingYouTubeTrack,
|
isPendingYouTubeTrack,
|
||||||
queueIdForTrack,
|
queueIdForTrack,
|
||||||
toQueueItems
|
toQueueItems,
|
||||||
|
youTubeThumbnailUrl
|
||||||
} from '$lib/player/player.svelte';
|
} from '$lib/player/player.svelte';
|
||||||
import { HUES, fmtTime } from '$lib/theme/color';
|
import { HUES, fmtTime } from '$lib/theme/color';
|
||||||
import Cover from '$lib/components/ui/Cover.svelte';
|
import Cover from '$lib/components/ui/Cover.svelte';
|
||||||
@@ -179,6 +180,7 @@
|
|||||||
<button type="button" onclick={() => playFrom(i)} class="flex min-w-0 items-center gap-3.5 text-left">
|
<button type="button" onclick={() => playFrom(i)} class="flex min-w-0 items-center gap-3.5 text-left">
|
||||||
<Cover
|
<Cover
|
||||||
trackId={track.id}
|
trackId={track.id}
|
||||||
|
src={isPendingYouTubeTrack(track) ? youTubeThumbnailUrl(track.externalId) : undefined}
|
||||||
hue={hueFor(track.id)}
|
hue={hueFor(track.id)}
|
||||||
size="small"
|
size="small"
|
||||||
alt={track.title}
|
alt={track.title}
|
||||||
|
|||||||
@@ -11,19 +11,34 @@
|
|||||||
const latest = $derived(data.latest);
|
const latest = $derived(data.latest);
|
||||||
const playlists = $derived(data.playlists);
|
const playlists = $derived(data.playlists);
|
||||||
const trackIds = $derived(data.trackIds);
|
const trackIds = $derived(data.trackIds);
|
||||||
|
interface RecentItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
artist?: string;
|
||||||
|
source: 'local' | 'youtube';
|
||||||
|
coverUrl?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const recentlyPlayed = $derived.by(() => {
|
const recentlyPlayed = $derived.by(() => {
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
const merged: { id: string; title: string }[] = [];
|
const merged: RecentItem[] = [];
|
||||||
for (const t of player.recentlyPlayed) {
|
for (const t of player.recentlyPlayed) {
|
||||||
const id = String(t.id);
|
const id = String(t.id);
|
||||||
if (seen.has(id)) continue;
|
if (seen.has(id)) continue;
|
||||||
seen.add(id);
|
seen.add(id);
|
||||||
merged.push({ id, title: t.title });
|
merged.push({ id, title: t.title, artist: t.artist, source: t.source, coverUrl: t.coverUrl });
|
||||||
}
|
}
|
||||||
for (const t of data.recentlyPlayed) {
|
for (const item of toQueueItems(data.recentlyPlayed)) {
|
||||||
if (seen.has(t.id)) continue;
|
const id = String(item.id);
|
||||||
seen.add(t.id);
|
if (seen.has(id)) continue;
|
||||||
merged.push({ id: t.id, title: t.title });
|
seen.add(id);
|
||||||
|
merged.push({
|
||||||
|
id,
|
||||||
|
title: item.title,
|
||||||
|
artist: item.artist,
|
||||||
|
source: item.source ?? 'local',
|
||||||
|
coverUrl: item.coverUrl
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return merged.slice(0, 15);
|
return merged.slice(0, 15);
|
||||||
});
|
});
|
||||||
@@ -98,6 +113,7 @@
|
|||||||
>
|
>
|
||||||
<Cover
|
<Cover
|
||||||
trackId={track.id}
|
trackId={track.id}
|
||||||
|
src={track.coverUrl}
|
||||||
hue={hueFor(track.id)}
|
hue={hueFor(track.id)}
|
||||||
size="small"
|
size="small"
|
||||||
alt={track.title}
|
alt={track.title}
|
||||||
@@ -109,6 +125,9 @@
|
|||||||
</Cover>
|
</Cover>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="truncate text-sm font-semibold text-[var(--mf-text)]">{track.title}</div>
|
<div class="truncate text-sm font-semibold text-[var(--mf-text)]">{track.title}</div>
|
||||||
|
{#if track.artist}
|
||||||
|
<div class="truncate text-xs text-neutral-500">{track.artist}</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
Reference in New Issue
Block a user