diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-16 12:10:25 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-16 12:10:25 +0200 |
| commit | 9cbff21274604e37c0986d57937deef85819c396 (patch) | |
| tree | bdfb55978bb105fd78d1d35db33ff8bb35457f67 /packages/meshbay-hub/src/meshbay_hub/static/app.js | |
| parent | 2e973795383b71f63ae9e3bef0e5dfc7930b4c90 (diff) | |
| download | meshbay-9cbff21274604e37c0986d57937deef85819c396.tar.gz | |
music: the playlist menus
One button in Music's sticky toolbar — load, create, delete, remove a
track, sync now — and "add to playlist" on every cover and row. Both
surfaces share one list, read from the manifest, so they open instantly
with every node offline and no body is fetched until one is wanted.
Submenus expand in place rather than flying out: the account menu's
language list already does this, and a flyout has nowhere to go at 400px.
The tracklist under "remove a track" loads when it is expanded.
A name is typed into a field. Electron has no prompt — it throws.
Also splits the two playback failures: a decode failure belongs to that
file and keeps the bounded counter, a connection failure belongs to the
group and skips all of its queued tracks at once. Six dead tracks are
one more than the bound, which is where a playlist would otherwise stop.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 15 |
1 files changed, 15 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index ef8d1e9..38314e7 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -19,6 +19,8 @@ import { startIdleWatch, markActive } from './idle.js'; import { GroupPage } from './group-page.js'; import { SearchPage, ConnectionPool } from './search-page.js'; import { MusicPlayerBar } from './music-player.js'; +import { NameModal } from './playlist-menu.js'; +import { saveQueueAsPlaylist } from './playlists.js'; import { IndexingDock } from './index-dock.js'; import { SettingsPage } from './settings-page.js'; import { ProfilePage } from './profile-page.js'; @@ -750,6 +752,11 @@ function App() { const handleStopMusic = useCallback(() => setMusicQueue(null), []); + // Saving the queue is a shell-level action because the queue is: the player + // bar outlives every page, and the account it belongs to is here. + const [saveQueue, setSaveQueue] = useState(null); + const handleSaveQueue = useCallback((rows) => setSaveQueue(rows), []); + const resolved = resolveTheme(theme); // The name of the last session. A failed renewal clears the stored session, @@ -1153,11 +1160,19 @@ function App() { </main> </div> ${user && html`<${IndexingDock} groups=${groups} />`} + ${saveQueue && user && html` + <${NameModal} title=${t('playlists.save_queue')} + onSubmit=${async (name) => { + await saveQueueAsPlaylist(user.userId, name, saveQueue); + }} + onClose=${() => setSaveQueue(null)} /> + `} ${musicQueue && html` <${MusicPlayerBar} getConnection=${getMusicConnection} queue=${musicQueue} userPrefs=${userPrefs} + onSaveQueue=${user ? handleSaveQueue : null} onClose=${handleStopMusic} /> `} <//> |