Add likes, follows and playlist visibility; redesign web-player UI
Backend: - New Likes feature (toggle/get liked tracks) and endpoints - User follow/unfollow, listening stats, public user profile - Playlist visibility (public/private) support - Various auth and DTO/response adjustments to support the above Frontend (web-player): - New design system pass: theme tokens/mode (light/dark), glass menu, tabs bar, player dock/extras/queue components, mix tiles, artist link - New routes: Liked, public user profile (/u/[id]), likes API proxy - Search refactor: shared SearchBox component used by TopBar and MobileHeader (search now reachable on mobile), live results as you type instead of requiring Enter, dropped the "Buscando…" quick-result preview - Removed now-unused components (MobileNav, CoverBadge, MixBentoTile, PlayPauseOverlay, SuggestionsTable, userDirectory) - Design handoff reference docs added under web-player/design_handoff_musify_player and web-player/docs/design-system-refactor-plan.md
This commit is contained in:
@@ -13,6 +13,7 @@ export interface PlayerTrack {
|
||||
source: TrackSourceKind;
|
||||
coverUrl?: string;
|
||||
explicit?: boolean;
|
||||
ownerUserId?: string | number | null;
|
||||
}
|
||||
|
||||
export interface Playlist {
|
||||
@@ -28,6 +29,7 @@ export interface QueueItem {
|
||||
source?: TrackSourceKind;
|
||||
coverUrl?: string;
|
||||
explicit?: boolean;
|
||||
ownerUserId?: string | number | null;
|
||||
}
|
||||
|
||||
export interface ApiTrackLike {
|
||||
@@ -38,6 +40,7 @@ export interface ApiTrackLike {
|
||||
externalId?: string | null;
|
||||
audioStatus?: 'Pending' | 'Processing' | 'Completed' | 'Failed';
|
||||
isExplicit?: boolean;
|
||||
ownerUserId?: string | number | null;
|
||||
}
|
||||
|
||||
function isYouTubeTrack(track: ApiTrackLike): boolean {
|
||||
@@ -69,7 +72,8 @@ export function toQueueItems(tracks: ApiTrackLike[]): QueueItem[] {
|
||||
? youTubeThumbnailUrl(track.externalId)
|
||||
: `/api/tracks/${track.id}/cover?size=small`
|
||||
: undefined,
|
||||
explicit: track.isExplicit
|
||||
explicit: track.isExplicit,
|
||||
ownerUserId: youTube ? undefined : track.ownerUserId
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -101,7 +105,8 @@ function toTrack(item: QueueItem): PlayerTrack {
|
||||
duration: 0,
|
||||
explicit: item.explicit,
|
||||
source: item.source ?? 'local',
|
||||
coverUrl: item.coverUrl
|
||||
coverUrl: item.coverUrl,
|
||||
ownerUserId: item.ownerUserId
|
||||
};
|
||||
}
|
||||
|
||||
@@ -115,6 +120,8 @@ class PlayerState {
|
||||
error = $state('');
|
||||
recentlyPlayed = $state<PlayerTrack[]>([]);
|
||||
playlists = $state<Playlist[]>([]);
|
||||
shuffle = $state(false);
|
||||
repeat = $state(false);
|
||||
|
||||
accentColor = $state<string | null>(null);
|
||||
|
||||
@@ -154,6 +161,11 @@ class PlayerState {
|
||||
});
|
||||
audio.addEventListener('ended', () => {
|
||||
this.#stopProgressLoop();
|
||||
if (this.repeat) {
|
||||
audio.currentTime = 0;
|
||||
audio.play().catch(() => {});
|
||||
return;
|
||||
}
|
||||
this.next();
|
||||
});
|
||||
audio.addEventListener('error', () => {
|
||||
@@ -397,10 +409,39 @@ class PlayerState {
|
||||
next() {
|
||||
if (this.tracks.length === 0) return;
|
||||
const idx = this.#index();
|
||||
this.currentId = this.tracks[(idx + 1) % this.tracks.length].id;
|
||||
let nextIdx = (idx + 1) % this.tracks.length;
|
||||
if (this.shuffle && this.tracks.length > 1) {
|
||||
do {
|
||||
nextIdx = Math.floor(Math.random() * this.tracks.length);
|
||||
} while (nextIdx === idx);
|
||||
}
|
||||
this.currentId = this.tracks[nextIdx].id;
|
||||
this.#loadCurrent();
|
||||
}
|
||||
|
||||
toggleShuffle() {
|
||||
this.shuffle = !this.shuffle;
|
||||
}
|
||||
|
||||
toggleRepeat() {
|
||||
this.repeat = !this.repeat;
|
||||
}
|
||||
|
||||
playQueueIndex(index: number) {
|
||||
const track = this.tracks[index];
|
||||
if (!track) return;
|
||||
if (this.currentId === track.id) {
|
||||
this.toggle();
|
||||
return;
|
||||
}
|
||||
this.currentId = track.id;
|
||||
this.#loadCurrent();
|
||||
}
|
||||
|
||||
clearUpcoming() {
|
||||
this.tracks = this.currentId === null ? [] : [this.current];
|
||||
}
|
||||
|
||||
previous() {
|
||||
if (this.tracks.length === 0) return;
|
||||
const audio = this.#audioEl();
|
||||
|
||||
Reference in New Issue
Block a user