Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enhancement]: Support toggling Album/Track view for gneres #591

Merged
merged 4 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@
"moreFromGeneric": "more from {{item}}"
},
"albumList": {
"artistAlbums": "Albums by {{artist}}",
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
"title": "$t(entity.album_other)"
},
"appMenu": {
Expand Down Expand Up @@ -336,6 +338,8 @@
"upNext": "up next"
},
"genreList": {
"showAlbums": "show $t(entity.genre_one) $t(entity.album_other)",
"showTracks": "show $t(entity.genre_one) $t(entity.track_other)",
"title": "$t(entity.genre_other)"
},
"globalSearch": {
Expand Down Expand Up @@ -474,6 +478,8 @@
"gaplessAudio": "gapless audio",
"gaplessAudio_description": "sets the gapless audio setting for mpv",
"gaplessAudio_optionWeak": "weak (recommended)",
"genreBehavior": "genre page default behavior",
"genreBehavior_description": "determines whether clicking on a genre opens by default in track or album list",
"globalMediaHotkeys": "global media hotkeys",
"globalMediaHotkeys_description": "enable or disable the usage of your system media hotkeys to control playback",
"homeConfiguration": "home page configuration",
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/components/virtual-table/cells/genre-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { generatePath, Link } from 'react-router-dom';
import type { AlbumArtist, Artist } from '/@/renderer/api/types';
import { Text } from '/@/renderer/components/text';
import { CellContainer } from '/@/renderer/components/virtual-table/cells/generic-cell';
import { AppRoute } from '/@/renderer/router/routes';
import { Separator } from '/@/renderer/components/separator';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';

export const GenreCell = ({ value, data }: ICellRendererParams) => {
const genrePath = useGenreRoute();
return (
<CellContainer $position="left">
<Text
Expand All @@ -24,9 +25,7 @@ export const GenreCell = ({ value, data }: ICellRendererParams) => {
component={Link}
overflow="hidden"
size="md"
to={generatePath(AppRoute.LIBRARY_GENRES_SONGS, {
genreId: item.id,
})}
to={generatePath(genrePath, { genreId: item.id })}
>
{item.name || '—'}
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
} from '/@/renderer/store/settings.store';
import { Play } from '/@/renderer/types';
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';

const isFullWidthRow = (node: RowNode) => {
return node.id?.startsWith('disc-');
Expand Down Expand Up @@ -81,6 +82,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
const isFocused = useAppFocus();
const currentSong = useCurrentSong();
const { externalLinks } = useGeneralSettings();
const genreRoute = useGenreRoute();

const columnDefs = useMemo(
() => getColumnDefs(tableConfig.columns, false, 'albumDetail'),
Expand Down Expand Up @@ -389,7 +391,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
component={Link}
radius={0}
size="md"
to={generatePath(AppRoute.LIBRARY_GENRES_SONGS, {
to={generatePath(genreRoute, {
genreId: genre.id,
})}
variant="outline"
Expand Down
54 changes: 25 additions & 29 deletions src/renderer/features/albums/components/album-list-header.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,59 @@
import type { ChangeEvent, MutableRefObject } from 'react';
import { useEffect, useRef, type ChangeEvent, type MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { Flex, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next';
import { useListFilterRefresh } from '../../../hooks/use-list-filter-refresh';
import { LibraryItem } from '/@/renderer/api/types';
import { PageHeader, SearchInput } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { useListContext } from '/@/renderer/context/list-context';
import { AlbumListHeaderFilters } from '/@/renderer/features/albums/components/album-list-header-filters';
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
import { useContainerQuery } from '/@/renderer/hooks';
import {
AlbumListFilter,
useCurrentServer,
useListStoreActions,
useListStoreByKey,
usePlayButtonBehavior,
} from '/@/renderer/store';
import { ListDisplayType } from '/@/renderer/types';
import { AlbumListFilter, useCurrentServer, usePlayButtonBehavior } from '/@/renderer/store';
import { titleCase } from '/@/renderer/utils';
import { useDisplayRefresh } from '/@/renderer/hooks/use-display-refresh';

interface AlbumListHeaderProps {
genreId?: string;
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>;
title?: string;
}

export const AlbumListHeader = ({ itemCount, gridRef, tableRef, title }: AlbumListHeaderProps) => {
export const AlbumListHeader = ({
genreId,
itemCount,
gridRef,
tableRef,
title,
}: AlbumListHeaderProps) => {
const { t } = useTranslation();
const server = useCurrentServer();
const { setFilter, setTablePagination } = useListStoreActions();
const cq = useContainerQuery();
const { pageKey, handlePlay } = useListContext();
const { display, filter } = useListStoreByKey({ key: pageKey });
const playButtonBehavior = usePlayButtonBehavior();

const { handleRefreshGrid, handleRefreshTable } = useListFilterRefresh({
const genreRef = useRef<string>();
const { filter, handlePlay, refresh, search } = useDisplayRefresh({
gridRef,
itemType: LibraryItem.ALBUM,
server,
tableRef,
});

const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
const searchTerm = e.target.value === '' ? undefined : e.target.value;
const updatedFilters = setFilter({
data: { searchTerm },
itemType: LibraryItem.ALBUM,
key: pageKey,
}) as AlbumListFilter;
const updatedFilters = search(e) as AlbumListFilter;

if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) {
handleRefreshTable(tableRef, updatedFilters);
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
} else {
handleRefreshGrid(gridRef, updatedFilters);
}
refresh(updatedFilters);
}, 500);

useEffect(() => {
if (genreRef.current && genreRef.current !== genreId) {
refresh(filter);
}

genreRef.current = genreId;
}, [filter, genreId, refresh, tableRef]);

return (
<Stack
ref={cq.ref}
Expand Down
56 changes: 50 additions & 6 deletions src/renderer/features/albums/routes/album-list-route.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useCallback, useMemo, useRef } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import isEmpty from 'lodash/isEmpty';
import { useTranslation } from 'react-i18next';
import { useParams, useSearchParams } from 'react-router-dom';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { LibraryItem } from '/@/renderer/api/types';
import { GenreListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { ListContext } from '/@/renderer/context/list-context';
import { AlbumListContent } from '/@/renderer/features/albums/components/album-list-content';
Expand All @@ -15,33 +16,68 @@ import { AnimatedPage } from '/@/renderer/features/shared';
import { queryClient } from '/@/renderer/lib/react-query';
import { useCurrentServer, useListFilterByKey } from '/@/renderer/store';
import { Play } from '/@/renderer/types';
import { useGenreList } from '/@/renderer/features/genres';
import { titleCase } from '/@/renderer/utils';

const AlbumListRoute = () => {
const { t } = useTranslation();
const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
const tableRef = useRef<AgGridReactType | null>(null);
const server = useCurrentServer();
const [searchParams] = useSearchParams();
const { albumArtistId } = useParams();
const { albumArtistId, genreId } = useParams();
const pageKey = albumArtistId ? `albumArtistAlbum` : 'album';
const handlePlayQueueAdd = usePlayQueueAdd();

const customFilters = useMemo(() => {
const value = {
...(albumArtistId && { artistIds: [albumArtistId] }),
...(genreId && {
_custom: {
jellyfin: {
GenreIds: genreId,
},
navidrome: {
genre_id: genreId,
},
},
}),
};

if (isEmpty(value)) {
return undefined;
}

return value;
}, [albumArtistId]);
}, [albumArtistId, genreId]);

const albumListFilter = useListFilterByKey({
filter: customFilters,
key: pageKey,
});

const genreList = useGenreList({
options: {
cacheTime: 1000 * 60 * 60,
enabled: !!genreId,
},
query: {
sortBy: GenreListSort.NAME,
sortOrder: SortOrder.ASC,
startIndex: 0,
},
serverId: server?.id,
});

const genreTitle = useMemo(() => {
if (!genreList.data) return '';
const genre = genreList.data.items.find((g) => g.id === genreId);

if (!genre) return 'Unknown';

return genre?.name;
}, [genreId, genreList.data]);

const itemCountCheck = useAlbumList({
options: {
cacheTime: 1000 * 60,
Expand Down Expand Up @@ -98,19 +134,27 @@ const AlbumListRoute = () => {
return {
customFilters,
handlePlay,
id: albumArtistId ?? undefined,
id: albumArtistId ?? genreId,
pageKey,
};
}, [albumArtistId, customFilters, handlePlay, pageKey]);
}, [albumArtistId, customFilters, genreId, handlePlay, pageKey]);

const artist = searchParams.get('artistName');
const title = artist
? t('page.albumList.artistAlbums', { artist })
: genreId
? t('page.albumList.genreAlbums', { genre: titleCase(genreTitle) })
: undefined;

return (
<AnimatedPage>
<ListContext.Provider value={providerValue}>
<AlbumListHeader
genreId={genreId}
gridRef={gridRef}
itemCount={itemCount}
tableRef={tableRef}
title={searchParams.get('artistName') || undefined}
title={title}
/>
<AlbumListContent
gridRef={gridRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { useCurrentServer } from '/@/renderer/store';
import { useGeneralSettings, usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { CardRow, Play, TableColumn } from '/@/renderer/types';
import { sanitize } from '/@/renderer/utils/sanitize';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';

const ContentContainer = styled.div`
position: relative;
Expand Down Expand Up @@ -69,6 +70,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
const cq = useContainerQuery();
const handlePlayQueueAdd = usePlayQueueAdd();
const server = useCurrentServer();
const genrePath = useGenreRoute();

const detailQuery = useAlbumArtistDetail({
query: { id: albumArtistId },
Expand Down Expand Up @@ -414,7 +416,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
component={Link}
radius="md"
size="md"
to={generatePath(AppRoute.LIBRARY_GENRES_SONGS, {
to={generatePath(genrePath, {
genreId: genre.id,
})}
variant="outline"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,15 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li
import { Flex, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next';
import { useListContext } from '../../../context/list-context';
import { useListStoreByKey } from '../../../store/list.store';
import { FilterBar } from '../../shared/components/filter-bar';
import { LibraryItem } from '/@/renderer/api/types';
import { PageHeader, SearchInput } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { AlbumArtistListHeaderFilters } from '/@/renderer/features/artists/components/album-artist-list-header-filters';
import { LibraryHeaderBar } from '/@/renderer/features/shared';
import { useContainerQuery } from '/@/renderer/hooks';
import { useListFilterRefresh } from '/@/renderer/hooks/use-list-filter-refresh';
import { AlbumArtistListFilter, useCurrentServer, useListStoreActions } from '/@/renderer/store';
import { ListDisplayType } from '/@/renderer/types';
import { AlbumArtistListFilter, useCurrentServer } from '/@/renderer/store';
import { useDisplayRefresh } from '/@/renderer/hooks/use-display-refresh';

interface AlbumArtistListHeaderProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
Expand All @@ -29,30 +26,18 @@ export const AlbumArtistListHeader = ({
}: AlbumArtistListHeaderProps) => {
const { t } = useTranslation();
const server = useCurrentServer();
const { pageKey } = useListContext();
const { display, filter } = useListStoreByKey({ key: pageKey });
const { setFilter, setTablePagination } = useListStoreActions();
const cq = useContainerQuery();

const { handleRefreshGrid, handleRefreshTable } = useListFilterRefresh({
const { filter, refresh, search } = useDisplayRefresh({
gridRef,
itemType: LibraryItem.ALBUM_ARTIST,
server,
tableRef,
});

const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
const searchTerm = e.target.value === '' ? undefined : e.target.value;
const updatedFilters = setFilter({
data: { searchTerm },
itemType: LibraryItem.ALBUM_ARTIST,
key: pageKey,
}) as AlbumArtistListFilter;

if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) {
handleRefreshTable(tableRef, updatedFilters);
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
} else {
handleRefreshGrid(gridRef, updatedFilters);
}
const updatedFilters = search(e) as AlbumArtistListFilter;
refresh(updatedFilters);
}, 500);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {
} from '/@/renderer/components/virtual-grid';
import { useListContext } from '/@/renderer/context/list-context';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
import { CardRow, ListDisplayType } from '/@/renderer/types';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';

export const GenreListGridView = ({ gridRef, itemCount }: any) => {
const queryClient = useQueryClient();
Expand All @@ -24,6 +24,7 @@ export const GenreListGridView = ({ gridRef, itemCount }: any) => {
const { pageKey, id } = useListContext();
const { grid, display, filter } = useListStoreByKey({ key: pageKey });
const { setGrid } = useListStoreActions();
const genrePath = useGenreRoute();

const [searchParams, setSearchParams] = useSearchParams();
const scrollOffset = searchParams.get('scrollOffset');
Expand Down Expand Up @@ -137,7 +138,7 @@ export const GenreListGridView = ({ gridRef, itemCount }: any) => {
loading={itemCount === undefined || itemCount === null}
minimumBatchSize={40}
route={{
route: AppRoute.LIBRARY_GENRES_SONGS,
route: genrePath,
slugs: [{ idProperty: 'id', slugProperty: 'genreId' }],
}}
width={width}
Expand Down
Loading
Loading