mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
add favorites list
This commit is contained in:
@@ -363,6 +363,9 @@
|
||||
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
|
||||
"title": "$t(entity.album_other)"
|
||||
},
|
||||
"favorites": {
|
||||
"title": "$t(entity.favorite_other)"
|
||||
},
|
||||
"appMenu": {
|
||||
"collapseSidebar": "collapse sidebar",
|
||||
"expandSidebar": "expand sidebar",
|
||||
@@ -497,6 +500,7 @@
|
||||
"albumArtists": "$t(entity.albumArtist_other)",
|
||||
"albums": "$t(entity.album_other)",
|
||||
"artists": "$t(entity.artist_other)",
|
||||
"favorites": "$t(entity.favorite_other)",
|
||||
"folders": "$t(entity.folder_other)",
|
||||
"genres": "$t(entity.genre_other)",
|
||||
"home": "$t(common.home)",
|
||||
|
||||
@@ -199,6 +199,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
_start: query.startIndex,
|
||||
library_id: getLibraryId(query.musicFolderId),
|
||||
name: query.searchTerm,
|
||||
starred: query.favorite,
|
||||
...query._custom,
|
||||
role: hasFeature(apiClientProps.server, ServerFeature.BFR) ? 'albumartist' : '',
|
||||
...excludeMissing(apiClientProps.server),
|
||||
@@ -330,6 +331,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
_start: query.startIndex,
|
||||
library_id: getLibraryId(query.musicFolderId),
|
||||
name: query.searchTerm,
|
||||
starred: query.favorite,
|
||||
...query._custom,
|
||||
role: query.role || undefined,
|
||||
...excludeMissing(apiClientProps.server),
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
||||
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
||||
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
||||
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||
import { AlbumArtistListSort } from '/@/shared/types/domain-types';
|
||||
import { AlbumArtistListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey } from '/@/shared/types/types';
|
||||
|
||||
export const useAlbumArtistListFilters = () => {
|
||||
const { sortBy } = useSortByFilter<AlbumArtistListSort>(null, ItemListKey.ALBUM_ARTIST);
|
||||
const { setSortBy, sortBy } = useSortByFilter<AlbumArtistListSort>(
|
||||
null,
|
||||
ItemListKey.ALBUM_ARTIST,
|
||||
);
|
||||
|
||||
const { sortOrder } = useSortOrderFilter(null, ItemListKey.ALBUM_ARTIST);
|
||||
const { setSortOrder, sortOrder } = useSortOrderFilter(null, ItemListKey.ALBUM_ARTIST);
|
||||
|
||||
const { searchTerm, setSearchTerm } = useSearchTermFilter('');
|
||||
|
||||
const clear = useCallback(() => {
|
||||
setSearchTerm(null);
|
||||
setSortBy(AlbumArtistListSort.NAME);
|
||||
setSortOrder(SortOrder.ASC);
|
||||
}, [setSearchTerm, setSortBy, setSortOrder]);
|
||||
|
||||
const query = {
|
||||
[FILTER_KEYS.SHARED.SEARCH_TERM]: searchTerm ?? undefined,
|
||||
[FILTER_KEYS.SHARED.SORT_BY]: sortBy ?? undefined,
|
||||
@@ -19,6 +30,7 @@ export const useAlbumArtistListFilters = () => {
|
||||
};
|
||||
|
||||
return {
|
||||
clear,
|
||||
query,
|
||||
setSearchTerm,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Suspense } from 'react';
|
||||
|
||||
import { useListContext } from '/@/renderer/context/list-context';
|
||||
import {
|
||||
AlbumListView,
|
||||
OverrideAlbumListQuery,
|
||||
} from '/@/renderer/features/albums/components/album-list-content';
|
||||
import {
|
||||
AlbumArtistListView,
|
||||
OverrideAlbumArtistListQuery,
|
||||
} from '/@/renderer/features/artists/components/album-artist-list-content';
|
||||
import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
|
||||
import {
|
||||
OverrideSongListQuery,
|
||||
SongListView,
|
||||
} from '/@/renderer/features/songs/components/song-list-content';
|
||||
import { useListSettings } from '/@/renderer/store';
|
||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey } from '/@/shared/types/types';
|
||||
|
||||
interface FavoritesContentProps {
|
||||
itemType: LibraryItem;
|
||||
}
|
||||
|
||||
export const FavoritesContent = ({ itemType }: FavoritesContentProps) => {
|
||||
return (
|
||||
<AnimatedPage>
|
||||
<Suspense fallback={<Spinner container />}>
|
||||
{itemType === LibraryItem.ALBUM && <AlbumFavorites />}
|
||||
{itemType === LibraryItem.SONG && <SongFavorites />}
|
||||
{itemType === LibraryItem.ALBUM_ARTIST && <ArtistFavorites />}
|
||||
</Suspense>
|
||||
</AnimatedPage>
|
||||
);
|
||||
};
|
||||
|
||||
const AlbumFavorites = () => {
|
||||
const { display, grid, itemsPerPage, pagination, table } = useListSettings(ItemListKey.ALBUM);
|
||||
const { customFilters } = useListContext();
|
||||
|
||||
const albumQuery: OverrideAlbumListQuery = {
|
||||
...(customFilters as OverrideAlbumListQuery),
|
||||
};
|
||||
|
||||
return (
|
||||
<AlbumListView
|
||||
display={display}
|
||||
grid={grid}
|
||||
itemsPerPage={itemsPerPage}
|
||||
overrideQuery={albumQuery}
|
||||
pagination={pagination}
|
||||
table={table}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SongFavorites = () => {
|
||||
const { display, grid, itemsPerPage, pagination, table } = useListSettings(ItemListKey.SONG);
|
||||
const { customFilters } = useListContext();
|
||||
|
||||
const songQuery: OverrideSongListQuery = {
|
||||
...(customFilters as OverrideSongListQuery),
|
||||
};
|
||||
|
||||
return (
|
||||
<SongListView
|
||||
display={display}
|
||||
grid={grid}
|
||||
itemsPerPage={itemsPerPage}
|
||||
overrideQuery={songQuery}
|
||||
pagination={pagination}
|
||||
table={table}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ArtistFavorites = () => {
|
||||
const { display, grid, itemsPerPage, pagination, table } = useListSettings(ItemListKey.ARTIST);
|
||||
const { customFilters } = useListContext();
|
||||
|
||||
const albumArtistQuery: OverrideAlbumArtistListQuery = {
|
||||
...(customFilters as OverrideAlbumArtistListQuery),
|
||||
};
|
||||
|
||||
return (
|
||||
<AlbumArtistListView
|
||||
display={display}
|
||||
grid={grid}
|
||||
itemsPerPage={itemsPerPage}
|
||||
overrideQuery={albumArtistQuery}
|
||||
pagination={pagination}
|
||||
table={table}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { PageHeader } from '/@/renderer/components/page-header/page-header';
|
||||
import { useListContext } from '/@/renderer/context/list-context';
|
||||
import { AlbumListHeaderFilters } from '/@/renderer/features/albums/components/album-list-header-filters';
|
||||
import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters';
|
||||
import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters';
|
||||
import { useAlbumArtistListFilters } from '/@/renderer/features/artists/hooks/use-album-artist-list-filters';
|
||||
import { FilterBar } from '/@/renderer/features/shared/components/filter-bar';
|
||||
import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar';
|
||||
import { ListSearchInput } from '/@/renderer/features/shared/components/list-search-input';
|
||||
import { SongListHeaderFilters } from '/@/renderer/features/songs/components/song-list-header-filters';
|
||||
import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters';
|
||||
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
||||
import { Flex } from '/@/shared/components/flex/flex';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { TextTitle } from '/@/shared/components/text-title/text-title';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
interface FavoritesHeaderProps {
|
||||
itemType: LibraryItem;
|
||||
}
|
||||
|
||||
export const FavoritesHeader = ({ itemType }: FavoritesHeaderProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { customFilters, itemCount } = useListContext();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const albumFilters = useAlbumListFilters();
|
||||
const albumArtistFilters = useAlbumArtistListFilters();
|
||||
const songFilters = useSongListFilters();
|
||||
|
||||
const playQuery = useMemo(() => {
|
||||
let query = {};
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
query = albumFilters.query;
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
query = albumArtistFilters.query;
|
||||
break;
|
||||
case LibraryItem.SONG:
|
||||
query = songFilters.query;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
...query,
|
||||
...(customFilters ?? {}),
|
||||
};
|
||||
}, [albumFilters.query, albumArtistFilters.query, songFilters.query, customFilters, itemType]);
|
||||
|
||||
const handleItemTypeChange = useCallback(
|
||||
(type: LibraryItem) => {
|
||||
albumFilters.clear();
|
||||
songFilters.clear();
|
||||
albumArtistFilters.clear();
|
||||
|
||||
// Clear all URL search params except 'type'
|
||||
navigate(`?type=${type}`, { replace: true });
|
||||
},
|
||||
[albumFilters, albumArtistFilters, songFilters, navigate],
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
<PageHeader>
|
||||
<Flex justify="space-between" w="100%">
|
||||
<LibraryHeaderBar ignoreMaxWidth>
|
||||
<PlayButton itemType={itemType} query={playQuery} />
|
||||
<LibraryHeaderBar.Title>
|
||||
<DropdownMenu position="right">
|
||||
<DropdownMenu.Target>
|
||||
<Stack gap={0} style={{ cursor: 'pointer' }}>
|
||||
<Group>
|
||||
<TextTitle isNoSelect order={3}>
|
||||
{t('page.favorites.title', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</TextTitle>
|
||||
<Icon icon="dropdown" size="xl" />
|
||||
</Group>
|
||||
<Text isMuted size="sm">
|
||||
{itemType === LibraryItem.ALBUM &&
|
||||
t('entity.album_other', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
{itemType === LibraryItem.ALBUM_ARTIST &&
|
||||
t('entity.artist_other', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
{itemType === LibraryItem.SONG &&
|
||||
t('entity.track_other', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</Text>
|
||||
</Stack>
|
||||
</DropdownMenu.Target>
|
||||
<DropdownMenu.Dropdown>
|
||||
<DropdownMenu.Item
|
||||
isSelected={itemType === LibraryItem.SONG}
|
||||
leftSection={<Icon icon="track" size="xl" />}
|
||||
onClick={() => handleItemTypeChange(LibraryItem.SONG)}
|
||||
>
|
||||
{t('entity.track_other', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
isSelected={itemType === LibraryItem.ALBUM}
|
||||
leftSection={<Icon icon="album" size="xl" />}
|
||||
onClick={() => handleItemTypeChange(LibraryItem.ALBUM)}
|
||||
>
|
||||
{t('entity.album_other', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
isSelected={itemType === LibraryItem.ALBUM_ARTIST}
|
||||
leftSection={<Icon icon="artist" size="xl" />}
|
||||
onClick={() =>
|
||||
handleItemTypeChange(LibraryItem.ALBUM_ARTIST)
|
||||
}
|
||||
>
|
||||
{t('entity.artist_other', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Dropdown>
|
||||
</DropdownMenu>
|
||||
</LibraryHeaderBar.Title>
|
||||
<LibraryHeaderBar.Badge isLoading={!itemCount}>
|
||||
{itemCount}
|
||||
</LibraryHeaderBar.Badge>
|
||||
</LibraryHeaderBar>
|
||||
<Group>
|
||||
<ListSearchInput />
|
||||
</Group>
|
||||
</Flex>
|
||||
</PageHeader>
|
||||
<FilterBar>
|
||||
{itemType === LibraryItem.ALBUM && <AlbumListHeaderFilters />}
|
||||
{itemType === LibraryItem.ALBUM_ARTIST && <AlbumArtistListHeaderFilters />}
|
||||
{itemType === LibraryItem.SONG && <SongListHeaderFilters />}
|
||||
</FilterBar>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const PlayButton = ({ itemType, query }: { itemType: LibraryItem; query: Record<string, any> }) => {
|
||||
return <LibraryHeaderBar.PlayButton itemType={itemType} listQuery={query} variant="filled" />;
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import { ListContext } from '/@/renderer/context/list-context';
|
||||
import { FavoritesContent } from '/@/renderer/features/favorites/components/favorites-content';
|
||||
import { FavoritesHeader } from '/@/renderer/features/favorites/components/favorites-header';
|
||||
import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
|
||||
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey } from '/@/shared/types/types';
|
||||
|
||||
const FavoritesRoute = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const itemType = (searchParams.get('type') as LibraryItem) || LibraryItem.SONG;
|
||||
|
||||
const [itemCount, setItemCount] = useState<number | undefined>(undefined);
|
||||
|
||||
const getPageKey = (type: LibraryItem): ItemListKey => {
|
||||
switch (type) {
|
||||
case LibraryItem.ALBUM:
|
||||
return ItemListKey.ALBUM;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
return ItemListKey.ALBUM_ARTIST;
|
||||
case LibraryItem.SONG:
|
||||
return ItemListKey.SONG;
|
||||
default:
|
||||
return ItemListKey.SONG;
|
||||
}
|
||||
};
|
||||
|
||||
const pageKey = useMemo(() => getPageKey(itemType), [itemType]);
|
||||
|
||||
const customFilters = useMemo(() => {
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
return { favorite: true };
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
return { favorite: true };
|
||||
case LibraryItem.SONG:
|
||||
return { favorite: true };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}, [itemType]);
|
||||
|
||||
const providerValue = useMemo(() => {
|
||||
return {
|
||||
customFilters,
|
||||
itemCount,
|
||||
pageKey,
|
||||
setItemCount,
|
||||
};
|
||||
}, [customFilters, itemCount, pageKey]);
|
||||
|
||||
return (
|
||||
<AnimatedPage>
|
||||
<ListContext.Provider value={providerValue}>
|
||||
<FavoritesHeader itemType={itemType} />
|
||||
<FavoritesContent itemType={itemType} />
|
||||
</ListContext.Provider>
|
||||
</AnimatedPage>
|
||||
);
|
||||
};
|
||||
|
||||
const FavoritesRouteWithBoundary = () => {
|
||||
return (
|
||||
<PageErrorBoundary>
|
||||
<FavoritesRoute />
|
||||
</PageErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
export default FavoritesRouteWithBoundary;
|
||||
@@ -10,6 +10,7 @@ const SIDEBAR_ITEMS: Array<[string, string]> = [
|
||||
['Now Playing', 'page.sidebar.nowPlaying'],
|
||||
['Playlists', 'page.sidebar.playlists'],
|
||||
['Search', 'page.sidebar.search'],
|
||||
['Favorites', 'page.sidebar.favorites'],
|
||||
['Settings', 'page.sidebar.settings'],
|
||||
['Tracks', 'page.sidebar.tracks'],
|
||||
];
|
||||
|
||||
@@ -32,6 +32,7 @@ export const CollapsedSidebar = () => {
|
||||
'\n',
|
||||
),
|
||||
'Artists-all': t('page.sidebar.artists', { postProcess: 'titleCase' }),
|
||||
Favorites: t('page.sidebar.favorites', { postProcess: 'titleCase' }),
|
||||
Folders: t('page.sidebar.folders', { postProcess: 'titleCase' }),
|
||||
Genres: t('page.sidebar.genres', { postProcess: 'titleCase' }),
|
||||
Home: t('page.sidebar.home', { postProcess: 'titleCase' }),
|
||||
|
||||
@@ -28,6 +28,7 @@ export const MobileSidebar = () => {
|
||||
Albums: t('page.sidebar.albums', { postProcess: 'titleCase' }),
|
||||
Artists: t('page.sidebar.albumArtists', { postProcess: 'titleCase' }),
|
||||
'Artists-all': t('page.sidebar.artists', { postProcess: 'titleCase' }),
|
||||
Favorites: t('page.sidebar.favorites', { postProcess: 'titleCase' }),
|
||||
Genres: t('page.sidebar.genres', { postProcess: 'titleCase' }),
|
||||
Home: t('page.sidebar.home', { postProcess: 'titleCase' }),
|
||||
'Now Playing': t('page.sidebar.nowPlaying', { postProcess: 'titleCase' }),
|
||||
@@ -105,7 +106,7 @@ export const MobileSidebar = () => {
|
||||
</Accordion>
|
||||
</ScrollArea>
|
||||
<div className={styles.serverSelectorWrapper}>
|
||||
<ServerSelector showImage={false} />
|
||||
<ServerSelector />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
RiFlag2Line,
|
||||
RiFolder3Fill,
|
||||
RiFolder3Line,
|
||||
RiHeartFill,
|
||||
RiHeartLine,
|
||||
RiHome6Fill,
|
||||
RiHome6Line,
|
||||
RiMusic2Fill,
|
||||
@@ -67,6 +69,10 @@ export const SidebarIcon = ({ active, route, size }: SidebarIconProps) => {
|
||||
if (active) return <RiSearchFill size={size} />;
|
||||
return <RiSearchLine size={size} />;
|
||||
default:
|
||||
if (route.startsWith(AppRoute.FAVORITES)) {
|
||||
if (active) return <RiHeartFill size={size} />;
|
||||
return <RiHeartLine size={size} />;
|
||||
}
|
||||
return <RiHome6Line size={size} />;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ export const Sidebar = () => {
|
||||
Albums: t('page.sidebar.albums', { postProcess: 'titleCase' }),
|
||||
Artists: t('page.sidebar.albumArtists', { postProcess: 'titleCase' }),
|
||||
'Artists-all': t('page.sidebar.artists', { postProcess: 'titleCase' }),
|
||||
Favorites: t('page.sidebar.favorites', { postProcess: 'titleCase' }),
|
||||
Genres: t('page.sidebar.genres', { postProcess: 'titleCase' }),
|
||||
Home: t('page.sidebar.home', { postProcess: 'titleCase' }),
|
||||
'Now Playing': t('page.sidebar.nowPlaying', { postProcess: 'titleCase' }),
|
||||
|
||||
@@ -70,6 +70,8 @@ const GenreDetailRoute = lazy(
|
||||
|
||||
const SearchRoute = lazy(() => import('/@/renderer/features/search/routes/search-route'));
|
||||
|
||||
const FavoritesRoute = lazy(() => import('/@/renderer/features/favorites/routes/favorites-route'));
|
||||
|
||||
export const AppRouter = () => {
|
||||
const router = (
|
||||
<HashRouter>
|
||||
@@ -90,6 +92,7 @@ export const AppRouter = () => {
|
||||
<Route element={<HomeRoute />} index />
|
||||
<Route element={<HomeRoute />} path={AppRoute.HOME} />
|
||||
<Route element={<SearchRoute />} path={AppRoute.SEARCH} />
|
||||
<Route element={<FavoritesRoute />} path={AppRoute.FAVORITES} />
|
||||
<Route
|
||||
element={<NowPlayingRoute />}
|
||||
path={AppRoute.NOW_PLAYING}
|
||||
|
||||
@@ -2,6 +2,7 @@ export enum AppRoute {
|
||||
ACTION_REQUIRED = '/action-required',
|
||||
EXPLORE = '/explore',
|
||||
FAKE_LIBRARY_ALBUM_DETAILS = '/library/albums/dummy/:albumId',
|
||||
FAVORITES = '/favorites',
|
||||
HOME = '/',
|
||||
LIBRARY_ALBUM_ARTISTS = '/library/album-artists',
|
||||
LIBRARY_ALBUM_ARTISTS_DETAIL = '/library/album-artists/:albumArtistId',
|
||||
|
||||
@@ -527,6 +527,12 @@ export const sidebarItems: SidebarItemType[] = [
|
||||
route: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.SONG }),
|
||||
},
|
||||
{ disabled: false, id: 'Home', label: i18n.t('page.sidebar.home'), route: AppRoute.HOME },
|
||||
{
|
||||
disabled: false,
|
||||
id: 'Favorites',
|
||||
label: i18n.t('page.sidebar.favorites'),
|
||||
route: AppRoute.FAVORITES,
|
||||
},
|
||||
{
|
||||
disabled: false,
|
||||
id: 'Albums',
|
||||
@@ -1291,10 +1297,19 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
|
||||
return state;
|
||||
}
|
||||
|
||||
if (version <= 10) {
|
||||
state.general.sidebarItems.push({
|
||||
disabled: false,
|
||||
id: 'Favorites',
|
||||
label: i18n.t('page.sidebar.favorites'),
|
||||
route: AppRoute.FAVORITES,
|
||||
});
|
||||
}
|
||||
|
||||
return persistedState;
|
||||
},
|
||||
name: 'store_settings',
|
||||
version: 10,
|
||||
version: 11,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -210,7 +210,6 @@ const normalizeSong = (
|
||||
releaseYear: item.year || null,
|
||||
sampleRate: item.sampleRate || null,
|
||||
size: item.size,
|
||||
streamUrl: `${server?.url}/rest/stream.view?id=${id}&v=1.13.0&c=Feishin&${server?.credential}`,
|
||||
tags: item.tags || null,
|
||||
trackNumber: item.trackNumber,
|
||||
updatedAt: item.updatedAt,
|
||||
|
||||
@@ -195,7 +195,7 @@ const normalizeSong = (
|
||||
tags: null,
|
||||
trackNumber: item.track || 1,
|
||||
updatedAt: '',
|
||||
userFavorite: item.starred || false,
|
||||
userFavorite: Boolean(item.starred) || false,
|
||||
userRating: item.userRating || null,
|
||||
};
|
||||
};
|
||||
@@ -232,7 +232,7 @@ const normalizeAlbumArtist = (
|
||||
playCount: null,
|
||||
similarArtists: [],
|
||||
songCount: null,
|
||||
userFavorite: false,
|
||||
userFavorite: Boolean(item.starred) || false,
|
||||
userRating: null,
|
||||
};
|
||||
};
|
||||
@@ -290,7 +290,7 @@ const normalizeAlbum = (
|
||||
) || [],
|
||||
tags: null,
|
||||
updatedAt: item.created,
|
||||
userFavorite: item.starred || false,
|
||||
userFavorite: Boolean(item.starred) || false,
|
||||
userRating: item.userRating || null,
|
||||
version: item.version || null,
|
||||
};
|
||||
|
||||
@@ -661,6 +661,7 @@ export type AlbumArtistListCountArgs = BaseEndpointArgs & {
|
||||
|
||||
export interface AlbumArtistListQuery extends BaseQuery<AlbumArtistListSort> {
|
||||
_custom?: Record<string, any>;
|
||||
favorite?: boolean;
|
||||
limit?: number;
|
||||
musicFolderId?: string | string[];
|
||||
searchTerm?: string;
|
||||
@@ -753,6 +754,7 @@ export type ArtistListCountArgs = BaseEndpointArgs & { query: ListCountQuery<Art
|
||||
|
||||
export interface ArtistListQuery extends BaseQuery<ArtistListSort> {
|
||||
_custom?: Record<string, any>;
|
||||
favorite?: boolean;
|
||||
limit?: number;
|
||||
musicFolderId?: string | string[];
|
||||
role?: string;
|
||||
|
||||
Reference in New Issue
Block a user