restructure files onto electron-vite boilerplate

This commit is contained in:
jeffvli 2025-05-18 14:03:18 -07:00
parent 91ce2cd8a1
commit 1cf587bc8f
457 changed files with 9927 additions and 11705 deletions

View file

@ -1,6 +1,6 @@
import { useMemo } from 'react';
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
import { Box, Grid, Group, Stack } from '@mantine/core';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FaLastfmSquare } from 'react-icons/fa';
import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
@ -8,6 +8,7 @@ import { SiMusicbrainz } from 'react-icons/si';
import { generatePath, useParams } from 'react-router';
import { createSearchParams, Link } from 'react-router-dom';
import styled from 'styled-components';
import {
Album,
AlbumArtist,
@ -35,12 +36,12 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
import { PlayButton, useCreateFavorite, useDeleteFavorite } from '/@/renderer/features/shared';
import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
import { useContainerQuery } from '/@/renderer/hooks';
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
import { AppRoute } from '/@/renderer/router/routes';
import { ArtistItem, 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;
@ -224,9 +225,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
</TextTitle>
<Button
compact
uppercase
component={Link}
to={artistDiscographyLink}
uppercase
variant="subtle"
>
{t('page.albumArtistDetail.viewDiscography')}
@ -385,8 +386,8 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
loading={
createFavoriteMutation.isLoading || deleteFavoriteMutation.isLoading
}
variant="subtle"
onClick={handleFavorite}
variant="subtle"
>
{detailQuery?.data?.userFavorite ? (
<RiHeartFill
@ -399,11 +400,11 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
</Button>
<Button
compact
variant="subtle"
onClick={(e) => {
if (!detailQuery?.data) return;
handleGeneralContextMenu(e, [detailQuery.data!]);
}}
variant="subtle"
>
<RiMoreFill size={20} />
</Button>
@ -412,18 +413,18 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
<Group spacing="md">
<Button
compact
uppercase
component={Link}
to={artistDiscographyLink}
uppercase
variant="subtle"
>
{t('page.albumArtistDetail.viewDiscography')}
</Button>
<Button
compact
uppercase
component={Link}
to={artistSongsLink}
uppercase
variant="subtle"
>
{t('page.albumArtistDetail.viewAllTracks')}
@ -434,9 +435,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
<Group spacing="sm">
{detailQuery?.data?.genres?.map((genre) => (
<Button
key={`genre-${genre.id}`}
compact
component={Link}
key={`genre-${genre.id}`}
radius="md"
size="md"
to={generatePath(genrePath, {
@ -523,8 +524,8 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
position="apart"
>
<Group
noWrap
align="flex-end"
noWrap
>
<TextTitle
order={2}
@ -536,7 +537,6 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
</TextTitle>
<Button
compact
uppercase
component={Link}
to={generatePath(
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS,
@ -544,6 +544,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
albumArtistId: routeId,
},
)}
uppercase
variant="subtle"
>
{t('page.albumArtistDetail.viewAll', {
@ -555,24 +556,24 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
<VirtualTable
autoFitColumns
autoHeight
columnDefs={topSongsColumnDefs}
context={{
itemType: LibraryItem.SONG,
}}
deselectOnClickOutside
enableCellChangeFlash={false}
getRowId={(data) => data.data.uniqueId}
onCellContextMenu={handleContextMenu}
onRowDoubleClicked={handleRowDoubleClick}
rowData={topSongs}
rowHeight={60}
rowSelection="multiple"
shouldUpdateSong
stickyHeader
suppressCellFocus
suppressHorizontalScroll
suppressLoadingOverlay
suppressRowDrag
columnDefs={topSongsColumnDefs}
context={{
itemType: LibraryItem.SONG,
}}
enableCellChangeFlash={false}
getRowId={(data) => data.data.uniqueId}
rowData={topSongs}
rowHeight={60}
rowSelection="multiple"
onCellContextMenu={handleContextMenu}
onRowDoubleClicked={handleRowDoubleClick}
/>
</Box>
</Grid.Col>

View file

@ -1,14 +1,16 @@
import { forwardRef, Fragment, Ref } from 'react';
import { Group, Rating, Stack } from '@mantine/core';
import { forwardRef, Fragment, Ref } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router';
import { useCurrentServer } from '../../../store/auth.store';
import { LibraryItem, ServerType } from '/@/renderer/api/types';
import { Text } from '/@/renderer/components';
import { useAlbumArtistDetail } from '/@/renderer/features/artists/queries/album-artist-detail-query';
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
import { AppRoute } from '/@/renderer/router/routes';
import { formatDurationString } from '/@/renderer/utils';
import { useCurrentServer } from '../../../store/auth.store';
interface AlbumArtistDetailHeaderProps {
background: string;
@ -72,10 +74,10 @@ export const AlbumArtistDetailHeader = forwardRef(
return (
<LibraryHeader
ref={ref}
background={background}
imageUrl={detailQuery?.data?.imageUrl}
item={{ route: AppRoute.LIBRARY_ALBUM_ARTISTS, type: LibraryItem.ALBUM_ARTIST }}
ref={ref}
title={detailQuery?.data?.name || ''}
>
<Stack>
@ -92,11 +94,11 @@ export const AlbumArtistDetailHeader = forwardRef(
<>
<Text $noSelect></Text>
<Rating
onChange={handleUpdateRating}
readOnly={
detailQuery?.isFetching || updateRatingMutation.isLoading
}
value={detailQuery?.data?.userRating || 0}
onChange={handleUpdateRating}
/>
</>
)}

View file

@ -1,7 +1,10 @@
import type { RowDoubleClickedEvent } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { MutableRefObject } from 'react';
import { useListContext } from '../../../context/list-context';
import { LibraryItem, QueueSong, SongListQuery } from '/@/renderer/api/types';
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
import { VirtualTable } from '/@/renderer/components/virtual-table';
@ -18,8 +21,8 @@ interface AlbumArtistSongListContentProps {
}
export const AlbumArtistDetailTopSongsListContent = ({
tableRef,
data,
tableRef,
}: AlbumArtistSongListContentProps) => {
const server = useCurrentServer();
const { id, pageKey } = useListContext();
@ -67,11 +70,11 @@ export const AlbumArtistDetailTopSongsListContent = ({
shouldUpdateSong
{...tableProps}
getRowId={(data) => data.data.uniqueId}
onRowDoubleClicked={handleRowDoubleClick}
rowClassRules={rowClassRules}
rowData={data}
rowModelType="clientSide"
rowSelection="multiple"
onRowDoubleClicked={handleRowDoubleClick}
/>
</VirtualGridAutoSizerContainer>
</>

View file

@ -1,7 +1,8 @@
import { useTranslation } from 'react-i18next';
import { RiAddBoxFill, RiAddCircleFill, RiMoreFill, RiPlayFill } from 'react-icons/ri';
import { QueueSong } from '/@/renderer/api/types';
import { Button, DropdownMenu, PageHeader, SpinnerIcon, Paper } from '/@/renderer/components';
import { Button, DropdownMenu, PageHeader, Paper, SpinnerIcon } from '/@/renderer/components';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { LibraryHeaderBar } from '/@/renderer/features/shared';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
@ -14,9 +15,9 @@ interface AlbumArtistDetailTopSongsListHeaderProps {
}
export const AlbumArtistDetailTopSongsListHeader = ({
title,
itemCount,
data,
itemCount,
title,
}: AlbumArtistDetailTopSongsListHeaderProps) => {
const { t } = useTranslation();
const handlePlayQueueAdd = usePlayQueueAdd();

View file

@ -1,10 +1,13 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { lazy, MutableRefObject, Suspense } from 'react';
import { useListStoreByKey } from '../../../store/list.store';
import { Spinner } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { ListDisplayType } from '/@/renderer/types';
import { useListStoreByKey } from '../../../store/list.store';
import { useListContext } from '/@/renderer/context/list-context';
import { ListDisplayType } from '/@/renderer/types';
const AlbumArtistListGridView = lazy(() =>
import('/@/renderer/features/artists/components/album-artist-list-grid-view').then(
@ -23,14 +26,14 @@ const AlbumArtistListTableView = lazy(() =>
);
interface AlbumArtistListContentProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>;
}
export const AlbumArtistListContent = ({
itemCount,
gridRef,
itemCount,
tableRef,
}: AlbumArtistListContentProps) => {
const { pageKey } = useListContext();

View file

@ -2,8 +2,10 @@ import { QueryKey, useQueryClient } from '@tanstack/react-query';
import { MutableRefObject, useCallback, useMemo } from 'react';
import AutoSizer, { Size } from 'react-virtualized-auto-sizer';
import { ListOnScrollProps } from 'react-window';
import { VirtualGridAutoSizerContainer } from '../../../components/virtual-grid/virtual-grid-wrapper';
import { useListStoreByKey } from '../../../store/list.store';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import {
@ -17,28 +19,28 @@ import { ALBUMARTIST_CARD_ROWS } from '/@/renderer/components';
import { VirtualInfiniteGrid, VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { useListContext } from '/@/renderer/context/list-context';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite';
import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, useListStoreActions } from '/@/renderer/store';
import { CardRow, ListDisplayType } from '/@/renderer/types';
import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite';
interface AlbumArtistListGridViewProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
itemCount?: number;
}
export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListGridViewProps) => {
export const AlbumArtistListGridView = ({ gridRef, itemCount }: AlbumArtistListGridViewProps) => {
const queryClient = useQueryClient();
const server = useCurrentServer();
const handlePlayQueueAdd = usePlayQueueAdd();
const { pageKey } = useListContext();
const { grid, display, filter } = useListStoreByKey<AlbumArtistListQuery>({ key: pageKey });
const { display, filter, grid } = useListStoreByKey<AlbumArtistListQuery>({ key: pageKey });
const { setGrid } = useListStoreActions();
const handleFavorite = useHandleFavorite({ gridRef, server });
const fetchInitialData = useCallback(() => {
const query: Omit<AlbumArtistListQuery, 'startIndex' | 'limit'> = {
const query: Omit<AlbumArtistListQuery, 'limit' | 'startIndex'> = {
...filter,
};
@ -109,6 +111,9 @@ export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListG
const rows: CardRow<AlbumArtist>[] = [ALBUMARTIST_CARD_ROWS.name];
switch (filter.sortBy) {
case AlbumArtistListSort.ALBUM_COUNT:
rows.push(ALBUMARTIST_CARD_ROWS.albumCount);
break;
case AlbumArtistListSort.DURATION:
rows.push(ALBUMARTIST_CARD_ROWS.duration);
break;
@ -116,9 +121,6 @@ export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListG
break;
case AlbumArtistListSort.NAME:
break;
case AlbumArtistListSort.ALBUM_COUNT:
rows.push(ALBUMARTIST_CARD_ROWS.albumCount);
break;
case AlbumArtistListSort.PLAY_COUNT:
rows.push(ALBUMARTIST_CARD_ROWS.playCount);
break;
@ -129,11 +131,11 @@ export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListG
break;
case AlbumArtistListSort.RECENTLY_ADDED:
break;
case AlbumArtistListSort.RELEASE_DATE:
break;
case AlbumArtistListSort.SONG_COUNT:
rows.push(ALBUMARTIST_CARD_ROWS.songCount);
break;
case AlbumArtistListSort.RELEASE_DATE:
break;
}
return rows;
@ -144,7 +146,6 @@ export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListG
<AutoSizer>
{({ height, width }: Size) => (
<VirtualInfiniteGrid
ref={gridRef}
cardRows={cardRows}
display={display || ListDisplayType.CARD}
fetchFn={fetch}
@ -159,12 +160,13 @@ export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListG
itemType={LibraryItem.ALBUM_ARTIST}
loading={itemCount === undefined || itemCount === null}
minimumBatchSize={40}
onScroll={handleGridScroll}
ref={gridRef}
route={{
route: AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'albumArtistId' }],
}}
width={width}
onScroll={handleGridScroll}
/>
)}
</AutoSizer>

View file

@ -1,12 +1,16 @@
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
import { IDatasource } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { IDatasource } from '@ag-grid-community/core';
import { Divider, Flex, Group, Stack } from '@mantine/core';
import { useQueryClient } from '@tanstack/react-query';
import debounce from 'lodash/debounce';
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { RiFolder2Line, RiMoreFill, RiRefreshLine, RiSettings3Fill } from 'react-icons/ri';
import { useListContext } from '../../../context/list-context';
import i18n from '/@/i18n/i18n';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import {
@ -28,7 +32,6 @@ import {
useListStoreByKey,
} from '/@/renderer/store';
import { ListDisplayType, TableColumn } from '/@/renderer/types';
import i18n from '/@/i18n/i18n';
const FILTERS = {
jellyfin: [
@ -116,7 +119,7 @@ const FILTERS = {
};
interface AlbumArtistListHeaderFiltersProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
tableRef: MutableRefObject<AgGridReactType | null>;
}
@ -128,10 +131,10 @@ export const AlbumArtistListHeaderFilters = ({
const queryClient = useQueryClient();
const server = useCurrentServer();
const { pageKey } = useListContext();
const { display, table, grid, filter } = useListStoreByKey<AlbumArtistListQuery>({
const { display, filter, grid, table } = useListStoreByKey<AlbumArtistListQuery>({
key: pageKey,
});
const { setFilter, setTable, setTablePagination, setDisplayType, setGrid } =
const { setDisplayType, setFilter, setGrid, setTable, setTablePagination } =
useListStoreActions();
const cq = useContainerQuery();
@ -376,10 +379,10 @@ export const AlbumArtistListHeaderFilters = ({
<DropdownMenu.Dropdown>
{FILTERS[server?.type as keyof typeof FILTERS].map((f) => (
<DropdownMenu.Item
key={`filter-${f.name}`}
$isActive={f.value === filter.sortBy}
value={f.value}
key={`filter-${f.name}`}
onClick={handleSetSortBy}
value={f.value}
>
{f.name}
</DropdownMenu.Item>
@ -388,8 +391,8 @@ export const AlbumArtistListHeaderFilters = ({
</DropdownMenu>
<Divider orientation="vertical" />
<OrderToggleButton
sortOrder={filter.sortOrder}
onToggle={handleToggleSortOrder}
sortOrder={filter.sortOrder}
/>
{server?.type === ServerType.JELLYFIN && (
<>
@ -408,10 +411,10 @@ export const AlbumArtistListHeaderFilters = ({
<DropdownMenu.Dropdown>
{musicFoldersQuery.data?.items.map((folder) => (
<DropdownMenu.Item
key={`musicFolder-${folder.id}`}
$isActive={filter.musicFolderId === folder.id}
value={folder.id}
key={`musicFolder-${folder.id}`}
onClick={handleSetMusicFolder}
value={folder.id}
>
{folder.name}
</DropdownMenu.Item>
@ -423,10 +426,10 @@ export const AlbumArtistListHeaderFilters = ({
<Divider orientation="vertical" />
<Button
compact
onClick={handleRefresh}
size="md"
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
variant="subtle"
onClick={handleRefresh}
>
<RiRefreshLine size="1.3rem" />
</Button>
@ -473,8 +476,8 @@ export const AlbumArtistListHeaderFilters = ({
</DropdownMenu.Label>
<DropdownMenu.Item
$isActive={display === ListDisplayType.CARD}
value={ListDisplayType.CARD}
onClick={handleSetViewType}
value={ListDisplayType.CARD}
>
{t('table.config.view.card', {
postProcess: 'sentenceCase',
@ -482,8 +485,8 @@ export const AlbumArtistListHeaderFilters = ({
</DropdownMenu.Item>
<DropdownMenu.Item
$isActive={display === ListDisplayType.POSTER}
value={ListDisplayType.POSTER}
onClick={handleSetViewType}
value={ListDisplayType.POSTER}
>
{t('table.config.view.poster', {
postProcess: 'sentenceCase',
@ -491,8 +494,8 @@ export const AlbumArtistListHeaderFilters = ({
</DropdownMenu.Item>
<DropdownMenu.Item
$isActive={display === ListDisplayType.TABLE}
value={ListDisplayType.TABLE}
onClick={handleSetViewType}
value={ListDisplayType.TABLE}
>
{t('table.config.view.table', {
postProcess: 'sentenceCase',
@ -563,8 +566,8 @@ export const AlbumArtistListHeaderFilters = ({
defaultValue={table?.columns.map(
(column) => column.column,
)}
width={300}
onChange={handleTableColumns}
width={300}
/>
<Group position="apart">
<Text>

View file

@ -1,27 +1,30 @@
import type { ChangeEvent, MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import type { ChangeEvent, MutableRefObject } from 'react';
import { Flex, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next';
import { FilterBar } from '../../shared/components/filter-bar';
import { AlbumArtistListQuery, 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 { AlbumArtistListFilter, useCurrentServer } from '/@/renderer/store';
import { useDisplayRefresh } from '/@/renderer/hooks/use-display-refresh';
import { AlbumArtistListFilter, useCurrentServer } from '/@/renderer/store';
interface AlbumArtistListHeaderProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>;
}
export const AlbumArtistListHeader = ({
itemCount,
gridRef,
itemCount,
tableRef,
}: AlbumArtistListHeaderProps) => {
const { t } = useTranslation();
@ -64,8 +67,8 @@ export const AlbumArtistListHeader = ({
<Group>
<SearchInput
defaultValue={filter.searchTerm}
openedWidth={cq.isMd ? 250 : cq.isSm ? 200 : 150}
onChange={handleSearch}
openedWidth={cq.isMd ? 250 : cq.isSm ? 200 : 150}
/>
</Group>
</Flex>

View file

@ -1,7 +1,10 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { MutableRefObject } from 'react';
import { useListContext } from '../../../context/list-context';
import { ARTIST_CONTEXT_MENU_ITEMS } from '../../context-menu/context-menu-items';
import { LibraryItem } from '/@/renderer/api/types';
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
import { VirtualTable } from '/@/renderer/components/virtual-table';

View file

@ -1,10 +1,13 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { lazy, MutableRefObject, Suspense } from 'react';
import { useListStoreByKey } from '../../../store/list.store';
import { Spinner } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { ListDisplayType } from '/@/renderer/types';
import { useListStoreByKey } from '../../../store/list.store';
import { useListContext } from '/@/renderer/context/list-context';
import { ListDisplayType } from '/@/renderer/types';
const ArtistListGridView = lazy(() =>
import('/@/renderer/features/artists/components/artist-list-grid-view').then((module) => ({
@ -19,12 +22,12 @@ const ArtistListTableView = lazy(() =>
);
interface ArtistListContentProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>;
}
export const ArtistListContent = ({ itemCount, gridRef, tableRef }: ArtistListContentProps) => {
export const ArtistListContent = ({ gridRef, itemCount, tableRef }: ArtistListContentProps) => {
const { pageKey } = useListContext();
const { display } = useListStoreByKey({ key: pageKey });
const isGrid = display === ListDisplayType.CARD || display === ListDisplayType.POSTER;

View file

@ -2,8 +2,10 @@ import { QueryKey, useQueryClient } from '@tanstack/react-query';
import { MutableRefObject, useCallback, useMemo } from 'react';
import AutoSizer, { Size } from 'react-virtualized-auto-sizer';
import { ListOnScrollProps } from 'react-window';
import { VirtualGridAutoSizerContainer } from '../../../components/virtual-grid/virtual-grid-wrapper';
import { useListStoreByKey } from '../../../store/list.store';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import {
@ -17,28 +19,28 @@ import { ALBUMARTIST_CARD_ROWS } from '/@/renderer/components';
import { VirtualInfiniteGrid, VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { useListContext } from '/@/renderer/context/list-context';
import { usePlayQueueAdd } from '/@/renderer/features/player';
import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite';
import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, useListStoreActions } from '/@/renderer/store';
import { CardRow, ListDisplayType } from '/@/renderer/types';
import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite';
interface ArtistListGridViewProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
itemCount?: number;
}
export const ArtistListGridView = ({ itemCount, gridRef }: ArtistListGridViewProps) => {
export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewProps) => {
const queryClient = useQueryClient();
const server = useCurrentServer();
const handlePlayQueueAdd = usePlayQueueAdd();
const { pageKey } = useListContext();
const { grid, display, filter } = useListStoreByKey<ArtistListQuery>({ key: pageKey });
const { display, filter, grid } = useListStoreByKey<ArtistListQuery>({ key: pageKey });
const { setGrid } = useListStoreActions();
const handleFavorite = useHandleFavorite({ gridRef, server });
const fetchInitialData = useCallback(() => {
const query: Omit<ArtistListQuery, 'startIndex' | 'limit'> = {
const query: Omit<ArtistListQuery, 'limit' | 'startIndex'> = {
...filter,
};
@ -109,6 +111,9 @@ export const ArtistListGridView = ({ itemCount, gridRef }: ArtistListGridViewPro
const rows: CardRow<AlbumArtist>[] = [ALBUMARTIST_CARD_ROWS.name];
switch (filter.sortBy) {
case ArtistListSort.ALBUM_COUNT:
rows.push(ALBUMARTIST_CARD_ROWS.albumCount);
break;
case ArtistListSort.DURATION:
rows.push(ALBUMARTIST_CARD_ROWS.duration);
break;
@ -116,9 +121,6 @@ export const ArtistListGridView = ({ itemCount, gridRef }: ArtistListGridViewPro
break;
case ArtistListSort.NAME:
break;
case ArtistListSort.ALBUM_COUNT:
rows.push(ALBUMARTIST_CARD_ROWS.albumCount);
break;
case ArtistListSort.PLAY_COUNT:
rows.push(ALBUMARTIST_CARD_ROWS.playCount);
break;
@ -129,11 +131,11 @@ export const ArtistListGridView = ({ itemCount, gridRef }: ArtistListGridViewPro
break;
case ArtistListSort.RECENTLY_ADDED:
break;
case ArtistListSort.RELEASE_DATE:
break;
case ArtistListSort.SONG_COUNT:
rows.push(ALBUMARTIST_CARD_ROWS.songCount);
break;
case ArtistListSort.RELEASE_DATE:
break;
}
return rows;
@ -144,7 +146,6 @@ export const ArtistListGridView = ({ itemCount, gridRef }: ArtistListGridViewPro
<AutoSizer>
{({ height, width }: Size) => (
<VirtualInfiniteGrid
ref={gridRef}
cardRows={cardRows}
display={display || ListDisplayType.CARD}
fetchFn={fetch}
@ -159,12 +160,13 @@ export const ArtistListGridView = ({ itemCount, gridRef }: ArtistListGridViewPro
itemType={LibraryItem.ARTIST}
loading={itemCount === undefined || itemCount === null}
minimumBatchSize={40}
onScroll={handleGridScroll}
ref={gridRef}
route={{
route: AppRoute.LIBRARY_ARTISTS_DETAIL,
slugs: [{ idProperty: 'id', slugProperty: 'artistId' }],
}}
width={width}
onScroll={handleGridScroll}
/>
)}
</AutoSizer>

View file

@ -1,12 +1,16 @@
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
import { IDatasource } from '@ag-grid-community/core';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { IDatasource } from '@ag-grid-community/core';
import { Divider, Flex, Group, Stack } from '@mantine/core';
import { useQueryClient } from '@tanstack/react-query';
import debounce from 'lodash/debounce';
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { RiFolder2Line, RiMoreFill, RiRefreshLine, RiSettings3Fill } from 'react-icons/ri';
import { useListContext } from '../../../context/list-context';
import i18n from '/@/i18n/i18n';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import {
@ -27,6 +31,7 @@ import {
} from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { ALBUMARTIST_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
import { useRoles } from '/@/renderer/features/artists/queries/roles-query';
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
import { useContainerQuery } from '/@/renderer/hooks';
import {
@ -36,8 +41,6 @@ import {
useListStoreByKey,
} from '/@/renderer/store';
import { ListDisplayType, TableColumn } from '/@/renderer/types';
import i18n from '/@/i18n/i18n';
import { useRoles } from '/@/renderer/features/artists/queries/roles-query';
const FILTERS = {
jellyfin: [
@ -124,7 +127,7 @@ const FILTERS = {
};
interface ArtistListHeaderFiltersProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
tableRef: MutableRefObject<AgGridReactType | null>;
}
@ -133,10 +136,10 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
const queryClient = useQueryClient();
const server = useCurrentServer();
const { pageKey } = useListContext();
const { display, table, grid, filter } = useListStoreByKey<ArtistListQuery>({
const { display, filter, grid, table } = useListStoreByKey<ArtistListQuery>({
key: pageKey,
});
const { setFilter, setTable, setTablePagination, setDisplayType, setGrid } =
const { setDisplayType, setFilter, setGrid, setTable, setTablePagination } =
useListStoreActions();
const cq = useContainerQuery();
const roles = useRoles({
@ -369,7 +372,7 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
}, [filter, handleFilterChange, queryClient, server?.id]);
const handleSetRole = useCallback(
(e: string | null) => {
(e: null | string) => {
const updatedFilters = setFilter({
data: {
role: e || '',
@ -403,10 +406,10 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
<DropdownMenu.Dropdown>
{FILTERS[server?.type as keyof typeof FILTERS].map((f) => (
<DropdownMenu.Item
key={`filter-${f.name}`}
$isActive={f.value === filter.sortBy}
value={f.value}
key={`filter-${f.name}`}
onClick={handleSetSortBy}
value={f.value}
>
{f.name}
</DropdownMenu.Item>
@ -415,8 +418,8 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
</DropdownMenu>
<Divider orientation="vertical" />
<OrderToggleButton
sortOrder={filter.sortOrder}
onToggle={handleToggleSortOrder}
sortOrder={filter.sortOrder}
/>
{server?.type === ServerType.JELLYFIN && (
<>
@ -435,10 +438,10 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
<DropdownMenu.Dropdown>
{musicFoldersQuery.data?.items.map((folder) => (
<DropdownMenu.Item
key={`musicFolder-${folder.id}`}
$isActive={filter.musicFolderId === folder.id}
value={folder.id}
key={`musicFolder-${folder.id}`}
onClick={handleSetMusicFolder}
value={folder.id}
>
{folder.name}
</DropdownMenu.Item>
@ -452,18 +455,18 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
<Divider orientation="vertical" />
<Select
data={roles.data}
value={filter.role}
onChange={handleSetRole}
value={filter.role}
/>
</>
)}
<Divider orientation="vertical" />
<Button
compact
onClick={handleRefresh}
size="md"
tooltip={{ label: t('common.refresh', { postProcess: 'titleCase' }) }}
variant="subtle"
onClick={handleRefresh}
>
<RiRefreshLine size="1.3rem" />
</Button>
@ -510,8 +513,8 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
</DropdownMenu.Label>
<DropdownMenu.Item
$isActive={display === ListDisplayType.CARD}
value={ListDisplayType.CARD}
onClick={handleSetViewType}
value={ListDisplayType.CARD}
>
{t('table.config.view.card', {
postProcess: 'sentenceCase',
@ -519,8 +522,8 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
</DropdownMenu.Item>
<DropdownMenu.Item
$isActive={display === ListDisplayType.POSTER}
value={ListDisplayType.POSTER}
onClick={handleSetViewType}
value={ListDisplayType.POSTER}
>
{t('table.config.view.poster', {
postProcess: 'sentenceCase',
@ -528,8 +531,8 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
</DropdownMenu.Item>
<DropdownMenu.Item
$isActive={display === ListDisplayType.TABLE}
value={ListDisplayType.TABLE}
onClick={handleSetViewType}
value={ListDisplayType.TABLE}
>
{t('table.config.view.table', {
postProcess: 'sentenceCase',
@ -593,8 +596,8 @@ export const ArtistListHeaderFilters = ({ gridRef, tableRef }: ArtistListHeaderF
defaultValue={table?.columns.map(
(column) => column.column,
)}
width={300}
onChange={handleTableColumns}
width={300}
/>
<Group position="apart">
<Text>

View file

@ -1,25 +1,28 @@
import type { ChangeEvent, MutableRefObject } from 'react';
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import type { ChangeEvent, MutableRefObject } from 'react';
import { Flex, Group, Stack } from '@mantine/core';
import debounce from 'lodash/debounce';
import { useTranslation } from 'react-i18next';
import { FilterBar } from '../../shared/components/filter-bar';
import { ArtistListQuery, LibraryItem } from '/@/renderer/api/types';
import { PageHeader, SearchInput } from '/@/renderer/components';
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
import { ArtistListHeaderFilters } from '/@/renderer/features/artists/components/artist-list-header-filters';
import { LibraryHeaderBar } from '/@/renderer/features/shared';
import { useContainerQuery } from '/@/renderer/hooks';
import { ArtistListFilter, useCurrentServer } from '/@/renderer/store';
import { useDisplayRefresh } from '/@/renderer/hooks/use-display-refresh';
import { ArtistListHeaderFilters } from '/@/renderer/features/artists/components/artist-list-header-filters';
import { ArtistListFilter, useCurrentServer } from '/@/renderer/store';
interface ArtistListHeaderProps {
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
itemCount?: number;
tableRef: MutableRefObject<AgGridReactType | null>;
}
export const ArtistListHeader = ({ itemCount, gridRef, tableRef }: ArtistListHeaderProps) => {
export const ArtistListHeader = ({ gridRef, itemCount, tableRef }: ArtistListHeaderProps) => {
const { t } = useTranslation();
const server = useCurrentServer();
const cq = useContainerQuery();
@ -60,8 +63,8 @@ export const ArtistListHeader = ({ itemCount, gridRef, tableRef }: ArtistListHea
<Group>
<SearchInput
defaultValue={filter.searchTerm}
openedWidth={cq.isMd ? 250 : cq.isSm ? 200 : 150}
onChange={handleSearch}
openedWidth={cq.isMd ? 250 : cq.isSm ? 200 : 150}
/>
</Group>
</Flex>

View file

@ -1,7 +1,10 @@
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
import { MutableRefObject } from 'react';
import { useListContext } from '../../../context/list-context';
import { ARTIST_CONTEXT_MENU_ITEMS } from '../../context-menu/context-menu-items';
import { LibraryItem } from '/@/renderer/api/types';
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
import { VirtualTable } from '/@/renderer/components/virtual-table';