From 0c6007e7c4be67a7e56576835ad7d7fa7df7f91d Mon Sep 17 00:00:00 2001 From: DiheChen Date: Mon, 9 Nov 2026 02:01:58 +0800 Subject: [PATCH] feat: save songs to my playlists from the player, queue and history --- web/src/features/history/history-page.tsx | 2 + web/src/features/playback/player-bar.tsx | 2 + .../playlists/create-playlist-form.tsx | 41 +++++ web/src/features/playlists/index.ts | 2 + web/src/features/playlists/playlist-cover.tsx | 26 +++ .../features/playlists/playlist-sort.test.ts | 69 ++++++++ web/src/features/playlists/playlist-sort.ts | 35 +++++ web/src/features/playlists/playlists.css | 24 +++ web/src/features/playlists/save-label.ts | 5 + .../features/playlists/save-to-playlist.tsx | 148 ++++++++++++++++++ web/src/features/queue/queue-page.tsx | 20 ++- 11 files changed, 368 insertions(+), 6 deletions(-) create mode 100644 web/src/features/playlists/create-playlist-form.tsx create mode 100644 web/src/features/playlists/playlist-cover.tsx create mode 100644 web/src/features/playlists/playlist-sort.test.ts create mode 100644 web/src/features/playlists/playlist-sort.ts create mode 100644 web/src/features/playlists/playlists.css create mode 100644 web/src/features/playlists/save-label.ts create mode 100644 web/src/features/playlists/save-to-playlist.tsx diff --git a/web/src/features/history/history-page.tsx b/web/src/features/history/history-page.tsx index 245d0c6..c0dcf39 100644 --- a/web/src/features/history/history-page.tsx +++ b/web/src/features/history/history-page.tsx @@ -2,6 +2,7 @@ import type { PlayRecord } from './records' import { CirclePlus, Download, History, Search, Trash2 } from 'lucide-react' import { useState } from 'react' import { roomStore, sameTrack } from '@/core/room' +import { SaveToPlaylistButton } from '@/features/playlists' import { enqueue, sourceName } from '@/features/queue' import { bookTitle } from '@/shared/lib/book-title' import { formatClock } from '@/shared/lib/time' @@ -46,6 +47,7 @@ function HistoryRow({ record, compact, connected, playing }: { record: PlayRecor await enqueue(record.track) }} /> +
+ {nowPlaying && } volumeStore.setMuted(!muted)} />
void, onCancel: () => void }) { + const [name, setName] = useState('') + const [saving, setSaving] = useState(false) + return ( +
{ + if (event.key !== 'Escape' || event.nativeEvent.isComposing) + return + // 拦下对话框的 Esc:在“保存到歌单”里,Esc 只回到列表。 + event.preventDefault() + onCancel() + }} + onSubmit={async (event) => { + event.preventDefault() + if (saving) + return + setSaving(true) + const playlist = await playlistsStore.create(name) + if (playlist) + onCreated(playlist) + else + setSaving(false) + }} + > + +
+ + +
+
+ ) +} diff --git a/web/src/features/playlists/index.ts b/web/src/features/playlists/index.ts index 277bca5..5d028a4 100644 --- a/web/src/features/playlists/index.ts +++ b/web/src/features/playlists/index.ts @@ -1 +1,3 @@ export { playlistsStore } from './playlists-store' +export { saveToPlaylistLabel } from './save-label' +export { SaveToPlaylistButton, SaveToPlaylistDialog } from './save-to-playlist' diff --git a/web/src/features/playlists/playlist-cover.tsx b/web/src/features/playlists/playlist-cover.tsx new file mode 100644 index 0000000..f967936 --- /dev/null +++ b/web/src/features/playlists/playlist-cover.tsx @@ -0,0 +1,26 @@ +import { Music2, Star } from 'lucide-react' +import { useState } from 'react' + +export function FavoritesMark({ size = 14 }: { size?: number }) { + return