Skip to content

Add list view + per-grouping persistence for the audiobooks library#585

Open
kevinheneveld wants to merge 2 commits into
Listenarrs:canaryfrom
kevinheneveld:feat/audiobooks-grouped-list-view
Open

Add list view + per-grouping persistence for the audiobooks library#585
kevinheneveld wants to merge 2 commits into
Listenarrs:canaryfrom
kevinheneveld:feat/audiobooks-grouped-list-view

Conversation

@kevinheneveld
Copy link
Copy Markdown
Contributor

@kevinheneveld kevinheneveld commented May 13, 2026

Summary

Two related changes to the audiobooks library's grouped views, combined into one PR (was previously split across #585 and #590; #590 is being closed in favor of this).

1. List view for grouped authors and series. The audiobooks toolbar exposes a list/grid view toggle, but the grouped views (?group=authors, ?group=series) only had a grid template. Toggling list mode while grouped was silently a no-op. This PR adds a parallel list rendering for grouped collections: one row per collection (cover thumb, collection name, book count). Clicking a row navigates to the collection page, matching the grid-card behavior.

2. Per-grouping persistence of the view mode. Previously the load-from-storage was nested inside an if (scrollContainer.value) block that only runs for the Books grouping — so switching to list mode while grouped by Author or Series, then navigating away, would silently revert to grid. This switches to three per-grouping keys (listenarr.viewMode.books, .authors, .series) and reads the relevant one unconditionally on mount + whenever groupBy changes. A one-time migration seeds all three keys from the legacy listenarr.viewMode value so existing users keep their preference.

Implementation notes

  • Template change is localized to the grouped-view block in fe/src/views/library/AudiobooksView.vue. The existing grid is wrapped in v-else; new v-if=\"viewMode === 'list'\" branch above it.
  • CSS reuses the existing .audiobooks-list / .audiobook-list-item patterns; collection rows have their own three-column grid template (cover / name / count) since they don't carry status badges or per-item actions.
  • Persistence watch moved out from inside the scrollContainer guard to a top-level watch, so it fires regardless of grouping. Removed the now-unused `stopPersistViewModeWatch` cleanup handle.
  • Initial viewMode is set inline at declaration: `ref<...>(loadViewModeFor(groupBy.value))`.
  • Migration is additive — the legacy key is never deleted, just no longer read once the per-grouping keys exist.
  • `viewMode` and `toggleViewMode` are exposed via `defineExpose` so tests can drive the view deterministically.

Test plan

  • `cd fe && npm run test:unit` — 14 / 14 AudiobooksView tests passing (4 new: list-view rendering for authors + series, per-grouping persistence, legacy migration)
  • `cd fe && npm run type-check` — clean
  • Rebased onto canary tip (post-1.0.1); resolved one spec-file conflict (type-field adjacency) and re-ported the persistence change onto the new initializeVirtualScroller refactor

Closes #595
Supersedes #590

@T4g1
Copy link
Copy Markdown
Contributor

T4g1 commented May 14, 2026

Small note regarding the issue linked to this: Is there a particular reason why you created it on the fork instead of here ? It might be better to open them here instead if you expect feedback on those (or perhaps there is a way to see forked repository issues from here ?)

@kevinheneveld
Copy link
Copy Markdown
Contributor Author

Good point. My workflow tracks issues on the fork as a working backlog, but you're right that anything expecting upstream feedback belongs here. I'll re-open this one on Listenarrs/Listenarr and relink it.

@kevinheneveld
Copy link
Copy Markdown
Contributor Author

Re-filed the linked issue here: #595 (was kevinheneveld#1). Will update the PR body to point at the new one.

@kevinheneveld kevinheneveld force-pushed the feat/audiobooks-grouped-list-view branch from 284cea4 to e965c30 Compare May 17, 2026 22:42
@kevinheneveld kevinheneveld force-pushed the feat/audiobooks-grouped-list-view branch from e965c30 to c365ece Compare May 19, 2026 16:14
@kevinheneveld kevinheneveld force-pushed the feat/audiobooks-grouped-list-view branch from c365ece to cd9b0d4 Compare May 27, 2026 17:01
@kevinheneveld kevinheneveld changed the title Add list view for grouped author/series in audiobooks library Add list view + per-grouping persistence for the audiobooks library May 27, 2026
@kevinheneveld kevinheneveld marked this pull request as ready for review May 27, 2026 17:12
Kevin Heneveld added 2 commits May 29, 2026 16:32
The audiobooks toolbar exposes a list/grid toggle, but the grouped view
template only rendered a grid — toggling to list mode while grouped by
author or series was silently ignored. The list template only existed
inside the `groupBy === 'books'` branch.

This adds a parallel list rendering for grouped collections:
- Row per collection (cover thumb, name, book count)
- Clicking a row navigates to the collection page (matches grid behavior)
- Keyboard accessible (Enter / Space activate the row)

CSS reuses the existing `.audiobooks-list` / `.audiobook-list-item`
patterns; the row layout has its own three-column grid template
(cover / name / count) since collection rows don't carry status badges
or per-item actions.

Tests added for both authors and series groupings in list mode.

(cherry picked from commit 284cea4)
The view mode toggle's persistence was previously gated by scrollContainer
being mounted, which only happens for groupBy === 'books'. So switching
to list mode while grouped by author or series, then navigating away and
back, would silently revert to grid.

Switches to three localStorage keys (one per grouping: books, authors,
series), reads the relevant key unconditionally on mount, and re-reads
when groupBy changes — so each grouping can independently remember
list vs grid. A one-time migration seeds all three keys from the legacy
listenarr.viewMode value when first encountered, so existing users keep
their preference.

Persistence watch moved to top level so it's no longer gated by the
scrollContainer guard.
@kevinheneveld kevinheneveld force-pushed the feat/audiobooks-grouped-list-view branch from cd9b0d4 to 7d4da6c Compare May 30, 2026 00:33
Copy link
Copy Markdown
Collaborator

@therobbiedavis therobbiedavis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking care of this; it was actually on my list of todo's eventually. I left a couple of inline comments around the new grouped-list image path: the fallback argument is currently not a real image URL, and the author list rows do not participate in the existing author-cover lookup flow used by grid cards.

groupBy === 'authors'
? getAuthorImageUrl(collection)
: collection.coverUrls && collection.coverUrls[0],
`${groupBy}-list:${collection.name}`,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getProtectedImageSrc() treats its second argument as the actual fallback image URL, not as an image/cache key. With the current value, coverless author/series rows get a src like authors-list:Author A or series-list:Series 1 until the image error handler replaces it. Could this pass getPlaceholderUrl() instead?

"
:alt="collection.name"
loading="lazy"
decoding="async"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new author list path bypasses the existing author-cover lookup behavior. Grid author cards call into ensureAuthorCover() through handleAuthorImageError() and the observeAuthorCards() marker selector at AudiobooksView.vue:1519, but these list rows only use the generic handleImageError and do not render a data-author-name target. For authors without authorAsins, list mode will stay on the placeholder instead of fetching the lookup cover.

I’d avoid fixing this only by swapping @error to handleAuthorImageError: once my previous comment changes the fallback to a valid placeholder, no error fires for missing covers, so lookup still won’t happen. A safer fix would make list author rows participate in the same lazy observer path, and add a test where an author has no authorAsins but apiService.getAuthorLookup() returns a cover.

@therobbiedavis therobbiedavis self-assigned this Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement] List view missing for ?group=authors and ?group=series

3 participants