feat(ui): remember 'items per page' selection across sessions (#5819)

* feat(ui): add perPageStore helper for items-per-page persistence

* feat(ui): persist items-per-page selection in localStorage

* feat(ui): enable per-page persistence on album grid, missing files and playlist tracks

* feat(ui): restore saved album grid page size on fresh load

* feat(ui): restore saved items-per-page on list load

* fix(ui): move defaultRowsPerPageOptions to perPageStore to satisfy react-refresh lint

* fix(ui): correct defaultRowsPerPageOptions import in List and add render test

* refactor(ui): default getStoredPerPage fallback to the first option

The fallback equalled options[0] at three of four call sites; default it so
those sites stop restating it. SongList and useAlbumsPerPage still pass an
explicit fallback where it legitimately differs from the first option.

* fix(ui): persist only user-selected page sizes, validate album size against width

Persisting on every pagination context value let a URL-injected perPage (e.g.
the perPage=15 album link in NowPlayingPanel) or a forced single-option mobile
grid overwrite the saved preference. Persist only a value that is an actual
option in a multi-option selector. Also validate the album grid's redux session
value against the current width so a size chosen at a wider breakpoint can't
leave an out-of-range selector.

* fix(ui): restore saved items-per-page on the radio list

RadioList passed a hard-coded perPage that overrode List's stored seed via the
props spread, so a radio-list page size was persisted but never restored. Seed
it from storage like the other list views.

* fix(ui): persist page size only on an actual selector change

Watching the pagination context value meant any valid value persisted itself:
loading a list at a breakpoint where the saved size is invalid stored the
responsive fallback, and opening a URL with a valid ?perPage= stored that too,
either way discarding the user's real preference. Inject a wrapped setPerPage
instead, so only the rows-per-page selector writes. This also drops the
option-validation heuristics, which the new trigger makes unnecessary.
This commit is contained in:
Deluan Quintão 2026-07-19 18:59:37 -04:00 • committed by GitHub
commit 6c2644d208
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 286 additions and 22 deletions

View file

@ -1,10 +1,15 @@
import { List, SizeField, useResourceRefresh } from '../common/index'
import {
List,
Pagination,
SizeField,
getStoredPerPage,
useResourceRefresh,
} from '../common/index'
import {
Datagrid,
DateField,
TextField,
downloadCSV,
Pagination,
Filter,
ReferenceInput,
useTranslate,
@ -49,8 +54,10 @@ const BulkActionButtons = (props) => (
</>
)
const missingPerPageOptions = [50, 100, 200]
const MissingPagination = (props) => (
<Pagination rowsPerPageOptions={[50, 100, 200]} {...props} />
<Pagination rowsPerPageOptions={missingPerPageOptions} {...props} />
)
const MissingFilesList = (props) => {
@ -63,7 +70,7 @@ const MissingFilesList = (props) => {
actions={<MissingListActions />}
filters={<MissingFilesFilter />}
bulkActionButtons={<BulkActionButtons />}
perPage={50}
perPage={getStoredPerPage('missing', missingPerPageOptions)}
pagination={<MissingPagination />}
>
<Datagrid>