feat(web): Better playlist editing UX — row-click multi-select + bulk actions#14393
Open
dylanjeffers wants to merge 9 commits into
Open
feat(web): Better playlist editing UX — row-click multi-select + bulk actions#14393dylanjeffers wants to merge 9 commits into
dylanjeffers wants to merge 9 commits into
Conversation
The "New" button in the playlist library sidebar (and the empty-state nav link) used to immediately create a playlist named "New Playlist" and route the user into the edit page. Replace that one-shot dispatch with a Create Playlist modal that lets the user set a title, optional description, and optional artwork before the playlist is created. - New `CreatePlaylistModal` slice via `createModal` helper, wired into the modals reducer/state. - New `CreatePlaylistModal` component (Harmony Modal + TextInput + TextArea + UploadArtwork) reusing the existing `resizeImage` pipeline. - The sidebar "New" popup item and the empty-library nav link both open the modal instead of dispatching `createPlaylist` directly. - Playlists still default to private (enforced by the existing `optimisticallySavePlaylist` saga) and the saga still routes the user to the new playlist page after creation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a "Duplicate Playlist" secondary action to the sidebar's New (+) popup menu. Opens a modal where the user pastes any public Audius playlist URL, sees a preview of the source playlist (title, description, cover art), and toggles per-field switches to customize what should differ in the copy. The duplicated playlist is created as private by default (enforced by the existing optimisticallySavePlaylist saga). Scope: - Metadata-only duplication for now. Tracks are not copied — a follow-up PR will support full duplicate including track contents. The modal surfaces this with a helper line so users know they need to add tracks separately. - Reuses the existing createPlaylist saga: when artwork is not customized, we pass the source playlist's cover_art_sizes CID through so the saga reuses the cover instead of treating it as a new upload. Implementation: - New `DuplicatePlaylistModal` Redux modal slice (createModal helper). - New `DuplicatePlaylistModal` component (Harmony Modal + TextInput + Switch + TextArea + Artwork + UploadArtwork). - Resolves pasted URL → permalink via `getPathFromPlaylistUrl`, then loads the source via `useCollectionByPermalink`. - Wires "Duplicate Playlist" into `CreatePlaylistLibraryItemButton`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new "Add Tracks by URL" affordance to the owner action row on
playlist detail pages. Clicking it opens a modal where the user pastes
Audius track links — line, comma, or tab separated — and submits to
batch-add them to the current playlist. Albums and DDEX-imported
collections are intentionally excluded.
Implementation details:
- New `AddTracksByUrlModal` Redux modal slice (createModal helper) wired
through types/parentSlice/reducers/index.
- New `AddTracksByUrlModal` component:
- Parses pasted text into a deduped list of permalinks via
`getPathFromTrackUrl`.
- Resolves them in one round-trip via `sdk.tracks.getBulkTracks`.
- Filters out tracks already in the playlist, enforces a 100-track
cap, and reports invalid/unresolved/duplicate/over-limit counts in a
single summary toast.
- Dispatches `addTrackToPlaylist` per track with a 30 ms gap so each
saga's optimistic update reads the previous one's state.
- `addTrackToPlaylist` now accepts `{ silent: true }` so the per-track
"Added track to playlist" toast can be suppressed during batch adds;
default behavior (single-track adds elsewhere) is unchanged.
- New `IconLink` button in `OwnerActionButtons` opens the modal,
prefilled with the current collection id. Hidden for albums and
DDEX-imported collections.
Scope notes:
- Resolution uses the existing `addTrackToPlaylist` saga path
(sequential dispatches with small delay). A future PR could replace
this with a dedicated `addTracksToPlaylistBatch` saga that issues a
single SDK update for cleaner semantics on large pastes.
- Larger track-curation features from the spec (multi-select, range
select, undo/redo, copy selected URLs, multi-row drag) are deferred
to follow-up PRs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promotes the duplicate-playlist flow from metadata-only to a true
duplicate that also copies every track from the source.
- New DUPLICATE_PLAYLIST action carries the source playlist id, the
composed form fields, the full source track id list, and an isAlbum
flag.
- New duplicatePlaylistSaga drives the full sequence: it dispatches the
existing createPlaylist / createAlbum saga with the first source
track as initTrackId, takes() the resulting CREATE_PLAYLIST_REQUESTED
to learn the new playlist id, then sequentially dispatches
addTrackToPlaylist({ silent: true }) for every remaining track with
a small inter-dispatch delay so each saga sees the previous
optimistic update. Closes with a single summary toast.
- DuplicatePlaylistModal now dispatches DUPLICATE_PLAYLIST and exposes
the actual track count to the user ("All N tracks will be copied")
instead of the previous "tracks not copied" note.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings playlist detail-page editing inline per the new UX spec. The
existing /edit route still works for advanced fields (audience,
price, genre, etc.), but the common metadata flow is now handled
directly on the detail page.
- New `PlaylistEditModeProvider` context holds the edit-mode flag,
the staged metadata draft, conflict status, and saving status. The
matching `usePlaylistEditMode` hook safely returns a no-op shape
when not inside the provider, so shared components stay
backwards-compatible.
- Wrapped the desktop CollectionPage in the provider and rendered a
sticky `PlaylistEditModeBar` at the page footer. The bar shows a
Discard / Apply pair when there are pending changes, a slim "no
changes yet" footer while in edit mode without changes, and a
conflict banner with a Reload action when the playlist was changed
remotely since edit mode started.
- The `EditButton` pencil in the owner action row now toggles inline
edit mode instead of routing to /edit (the legacy link is kept as
a fallback when no provider is mounted).
- CollectionHeader (desktop) renders an inline `TextInput` for the
title, a `TextArea` for the description, and a `Switch` for
visibility while in edit mode. Otherwise behavior is unchanged.
- Desktop Artwork supports inline upload (file picker) when edit
mode is on; the staged image previews immediately and is sent
through on Apply along with the metadata draft.
- Apply checks the collection's `updated_at` against the timestamp
captured when edit mode started; if it has advanced, the bar
flips to the conflict state and aborts the save.
- Success messages are specific ("Saved details", "Saved artwork",
or "Saved details and artwork") based on which fields the user
changed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… undo/redo)
Adds the bulk track-curation toolbar to the playlist detail page when
in edit mode, plus keyboard shortcuts for the common operations.
- New `TrackSelectionProvider` + `useTrackSelection` hook: a Set of
selected track IDs with toggle (including shift-range across an
ordered id list), select-all, clear, and a "last clicked index" ref
so range select works in chronological click order.
- New `TrackHistoryProvider` + `useTrackHistoryContext`: undo/redo
stacks for `remove` and `add` operations. Undo of a `remove`
dispatches `addTrackToPlaylist({ silent: true })` (note: the
existing saga appends rather than re-inserting at the original
index — current ordering is a known limitation surfaced as a
follow-up).
- New `TrackBulkActionsBar` sticks to the top of the track table
while in edit mode and shows a count of selected tracks plus the
five bulk actions: Copy URLs (clipboard-writes
`${origin}${permalink}` for each selected track), Remove
(dispatches removeTrackFromPlaylist per id and pushes history
entries so each remove is undoable), Undo, Redo, and Select all /
Clear pair.
- Keyboard shortcuts wired through the same component while in edit
mode: Cmd/Ctrl+A select all, Cmd/Ctrl+Z undo, Cmd/Ctrl+Shift+Z /
Cmd/Ctrl+Y redo, Escape clears selection, Delete/Backspace removes
selected (and skips when focus is in a text input).
- CollectionPage is wrapped in the selection + history providers
alongside the existing edit-mode provider.
Scope notes — per-row checkbox UI and shift-range row-click select
require deeper changes to the existing TracksTable component and are
deferred to a follow-up PR; users can still select-all via the bar
or Cmd/Ctrl+A and operate on the full set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
While the playlist detail page is in edit mode, clicking a track row
toggles its selection in the bulk-actions context instead of playing
the track. Holding shift while clicking extends the selection over
the range between the previous click and the new one.
- New `EditAwareTracksTable` wrapper around the standard
`TracksTable`. It captures the global shift-key state with a
window listener (TracksTable's onClickRow does not pass a
MouseEvent) and rewrites `onClickRow` to call
`selection.toggle(id, index, { shift })` when edit mode is active.
- Outside of edit mode the wrapper is a transparent pass-through and
the existing play-on-click behavior is preserved.
- Desktop `CollectionPage` swaps its `TracksTable` usage for the new
edit-aware wrapper.
Combined with the bulk-actions bar from the previous PR, the user
can now: shift-click a range, Cmd/Ctrl+A to select all, Escape to
clear, Delete to remove, Cmd/Ctrl+Z/Y for undo/redo, and the bar's
Copy URLs / Remove buttons for bulk operations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Builds on the row-click selection from this branch to give the user visible feedback for which tracks are currently selected. - `TracksTable` gains an optional `rowClassNameAddition(track, index)` prop that's composed with the table's existing per-row className (used internally for the locked/disabled states). The hook is ref-stable so external state changes don't force a full re-render of the table machinery. - `EditAwareTracksTable` passes a `rowClassNameAddition` that returns the new `selected` CSS class when the row's track id is in the selection set and the page is in edit mode. The class draws a surface-2 background fill and a 3px accent bar on the left edge so selected rows are immediately scannable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Two small correctness fixes on top of the existing row-click selection in playlist edit mode. - `EditAwareTracksTable` now resets its shift-key ref on `window` blur. Previously, holding Shift while Cmd/Alt-Tabbing away would leave the ref stuck true (the keyup fires in the other window), so the next click on return was interpreted as a shift-extend. - `TrackSelectionContext.toggle` now keeps the anchor row stable across a sequence of shift-clicks. The anchor only moves on a plain click. This matches Finder / Gmail / Drive: click A, shift-click C selects A..C; a follow-up shift-click E then selects A..E (not C..E). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the Better playlist editing UX spec for the desktop playlist detail page. While in edit mode, clicking a track row toggles selection (shift+click extends the range), selected rows are visually highlighted, and a bulk-actions bar at the top of the table operates on the selection (remove, copy URLs, undo/redo). All standard editor flows (drag-reorder, single-row remove via overflow menu) keep working.
The branch stacks 8 small commits that build the whole spec:
feat(web): replace quick-create playlist flow with a modalfeat(web): add duplicate playlist flow under the New nav buttonfeat(web): add tracks to a playlist by pasting Audius URLsfeat(web): copy source tracks when duplicating a playlistfeat(web): inline edit mode on playlist detail page with staged Applyfeat(web): bulk track actions toolbar (select-all, copy URLs, remove, undo/redo)feat(web): row-click selection on playlist tracks in edit modefeat(web): highlight selected playlist rows while in edit modeThe interesting moving pieces of the row-click + highlight work:
EditAwareTracksTablewraps the standardTracksTableand, while the page is in edit mode, rewritesonClickRowto callselection.toggle(id, index, { shift }). SinceTracksTable.onClickRowdoes not forward theMouseEvent, the wrapper captures shift state from a windowkeydown/keyuplistener.TrackSelectionContextowns the selected-id set and the anchor index for shift-click ranges;toggle()walks[lastIndex..index]against the page'sorderedIdsfor range fills.TracksTablegains an optional, ref-stablerowClassNameAddition(track, index)prop that composes with its existing per-row className (used internally for the locked/disabled states).EditAwareTracksTableuses it to apply aselectedclass (surface-2fill + 3px accent bar) for selected rows.TrackBulkActionsBar) only bind while editing this collection: ⌘/Ctrl+A select all, Esc clear, Delete/Backspace remove, ⌘/Ctrl+Z/Y undo/redo.EditAwareTracksTableis a transparent pass-through and play-on-click is preserved.Test plan