diff --git a/ui/src/album/AlbumList.jsx b/ui/src/album/AlbumList.jsx
index 0b8c256df..a860c85bb 100644
--- a/ui/src/album/AlbumList.jsx
+++ b/ui/src/album/AlbumList.jsx
@@ -28,7 +28,11 @@ import {
import AlbumListActions from './AlbumListActions'
import AlbumTableView from './AlbumTableView'
import AlbumGridView from './AlbumGridView'
-import albumLists, { defaultAlbumList } from './albumLists'
+import albumLists from './albumLists'
+import {
+ getStoredDefaultView,
+ isResourceDefaultView,
+} from '../personal/defaultViews'
import config from '../config'
import AlbumInfo from './AlbumInfo'
import ExpandInfoDialog from '../dialogs/ExpandInfoDialog'
@@ -220,8 +224,10 @@ const AlbumList = (props) => {
// If it does not have filter/sort params (usually coming from Menu),
// reload with correct filter/sort params
if (!location.search) {
- const type =
- albumListType || localStorage.getItem('defaultView') || defaultAlbumList
+ const type = albumListType || getStoredDefaultView()
+ if (isResourceDefaultView(type)) {
+ return
+ }
const listParams = albumLists[type]
if (type === 'random') {
refresh()
diff --git a/ui/src/personal/SelectDefaultView.jsx b/ui/src/personal/SelectDefaultView.jsx
index 71c87305c..e90fd65bc 100644
--- a/ui/src/personal/SelectDefaultView.jsx
+++ b/ui/src/personal/SelectDefaultView.jsx
@@ -1,13 +1,10 @@
import { SelectInput, useTranslate } from 'react-admin'
-import albumLists, { defaultAlbumList } from '../album/albumLists'
+import { getDefaultViewChoices, getStoredDefaultView } from './defaultViews'
export const SelectDefaultView = (props) => {
const translate = useTranslate()
- const current = localStorage.getItem('defaultView') || defaultAlbumList
- const choices = Object.keys(albumLists).map((type) => ({
- id: type,
- name: translate(`resources.album.lists.${type}`),
- }))
+ const current = getStoredDefaultView()
+ const choices = getDefaultViewChoices(translate)
return (
+ resourceDefaultViews.includes(defaultView)
+
+export const getDefaultViewChoices = (translate) => [
+ ...Object.keys(albumLists).map((type) => ({
+ id: type,
+ name: translate(`resources.album.lists.${type}`),
+ })),
+ ...resourceDefaultViews.map((resource) => ({
+ id: resource,
+ name: translate(`resources.${resource}.name`, { smart_count: 2 }),
+ })),
+]
+
+export const getStoredDefaultView = () =>
+ localStorage.getItem('defaultView') || defaultAlbumList
diff --git a/ui/src/personal/defaultViews.test.js b/ui/src/personal/defaultViews.test.js
new file mode 100644
index 000000000..44057a736
--- /dev/null
+++ b/ui/src/personal/defaultViews.test.js
@@ -0,0 +1,48 @@
+import {
+ getDefaultViewChoices,
+ getStoredDefaultView,
+ isResourceDefaultView,
+ resourceDefaultViews,
+} from './defaultViews'
+import albumLists, { defaultAlbumList } from '../album/albumLists'
+
+describe('defaultViews', () => {
+ beforeEach(() => {
+ localStorage.clear()
+ })
+
+ it('includes album lists and top-level resource lists as choices', () => {
+ const choices = getDefaultViewChoices((key, options) =>
+ options?.smart_count ? `${key}:${options.smart_count}` : key,
+ )
+
+ expect(choices.map((choice) => choice.id)).toEqual([
+ ...Object.keys(albumLists),
+ ...resourceDefaultViews,
+ ])
+ expect(choices).toEqual(
+ expect.arrayContaining([
+ { id: 'artist', name: 'resources.artist.name:2' },
+ { id: 'song', name: 'resources.song.name:2' },
+ { id: 'playlist', name: 'resources.playlist.name:2' },
+ ]),
+ )
+ })
+
+ it('identifies resource-backed default views', () => {
+ expect(isResourceDefaultView('artist')).toBe(true)
+ expect(isResourceDefaultView('song')).toBe(true)
+ expect(isResourceDefaultView('playlist')).toBe(true)
+ expect(isResourceDefaultView('recentlyAdded')).toBe(false)
+ })
+
+ it('falls back to the default album list when no default view is stored', () => {
+ expect(getStoredDefaultView()).toBe(defaultAlbumList)
+ })
+
+ it('returns the stored default view', () => {
+ localStorage.setItem('defaultView', 'playlist')
+
+ expect(getStoredDefaultView()).toBe('playlist')
+ })
+})