mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 18:33:33 +00:00
restructure files onto electron-vite boilerplate
This commit is contained in:
parent
91ce2cd8a1
commit
1cf587bc8f
457 changed files with 9927 additions and 11705 deletions
|
|
@ -1,8 +1,9 @@
|
|||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import { RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { RowDoubleClickedEvent, RowHeightParams, RowNode } from '@ag-grid-community/core';
|
||||
import { Box, Group, Stack } from '@mantine/core';
|
||||
import { useSetState } from '@mantine/hooks';
|
||||
import { MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaLastfmSquare } from 'react-icons/fa';
|
||||
import { RiHeartFill, RiHeartLine, RiMoreFill, RiSettings2Fill } from 'react-icons/ri';
|
||||
|
|
@ -10,6 +11,7 @@ import { SiMusicbrainz } from 'react-icons/si';
|
|||
import { generatePath, useParams } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import {
|
||||
AlbumListQuery,
|
||||
|
|
@ -21,9 +23,9 @@ import {
|
|||
import { Button, Popover, Spoiler } from '/@/renderer/components';
|
||||
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel';
|
||||
import {
|
||||
getColumnDefs,
|
||||
TableConfigDropdown,
|
||||
VirtualTable,
|
||||
getColumnDefs,
|
||||
} from '/@/renderer/components/virtual-table';
|
||||
import { FullWidthDiscCell } from '/@/renderer/components/virtual-table/cells/full-width-disc-cell';
|
||||
import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles';
|
||||
|
|
@ -41,6 +43,7 @@ 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 { useAppFocus, useContainerQuery } from '/@/renderer/hooks';
|
||||
import { useGenreRoute } from '/@/renderer/hooks/use-genre-route';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer, useCurrentSong, useCurrentStatus } from '/@/renderer/store';
|
||||
import {
|
||||
|
|
@ -51,7 +54,6 @@ 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-');
|
||||
|
|
@ -75,7 +77,7 @@ interface AlbumDetailContentProps {
|
|||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentProps) => {
|
||||
export const AlbumDetailContent = ({ background, tableRef }: AlbumDetailContentProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { albumId } = useParams() as { albumId: string };
|
||||
const server = useCurrentServer();
|
||||
|
|
@ -112,7 +114,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||
}
|
||||
|
||||
let discNumber = -1;
|
||||
let discSubtitle: string | null = null;
|
||||
let discSubtitle: null | string = null;
|
||||
|
||||
const rowData: (QueueSong | { id: string; name: string })[] = [];
|
||||
const discTranslated = t('common.disc', { postProcess: 'upperCase' });
|
||||
|
|
@ -343,8 +345,8 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||
createFavoriteMutation.isLoading ||
|
||||
deleteFavoriteMutation.isLoading
|
||||
}
|
||||
variant="subtle"
|
||||
onClick={handleFavorite}
|
||||
variant="subtle"
|
||||
>
|
||||
{detailQuery?.data?.userFavorite ? (
|
||||
<RiHeartFill
|
||||
|
|
@ -357,11 +359,11 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||
</Button>
|
||||
<Button
|
||||
compact
|
||||
variant="subtle"
|
||||
onClick={(e) => {
|
||||
if (!detailQuery?.data) return;
|
||||
handleGeneralContextMenu(e, [detailQuery.data!]);
|
||||
}}
|
||||
variant="subtle"
|
||||
>
|
||||
<RiMoreFill size={20} />
|
||||
</Button>
|
||||
|
|
@ -388,9 +390,9 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||
<Group spacing="sm">
|
||||
{detailQuery?.data?.genres?.map((genre) => (
|
||||
<Button
|
||||
key={`genre-${genre.id}`}
|
||||
compact
|
||||
component={Link}
|
||||
key={`genre-${genre.id}`}
|
||||
radius={0}
|
||||
size="md"
|
||||
to={generatePath(genreRoute, {
|
||||
|
|
@ -451,15 +453,8 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||
)}
|
||||
<Box style={{ minHeight: '300px' }}>
|
||||
<VirtualTable
|
||||
key={`table-${tableConfig.rowHeight}`}
|
||||
ref={tableRef}
|
||||
autoHeight
|
||||
shouldUpdateSong
|
||||
stickyHeader
|
||||
suppressCellFocus
|
||||
suppressLoadingOverlay
|
||||
suppressRowDrag
|
||||
autoFitColumns={tableConfig.autoFit}
|
||||
autoHeight
|
||||
columnDefs={columnDefs}
|
||||
context={{
|
||||
currentSong,
|
||||
|
|
@ -479,17 +474,24 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||
if (isFullWidthRow(data.data)) return false;
|
||||
return true;
|
||||
}}
|
||||
rowClassRules={rowClassRules}
|
||||
rowData={songsRowData}
|
||||
rowSelection="multiple"
|
||||
key={`table-${tableConfig.rowHeight}`}
|
||||
onCellContextMenu={onCellContextMenu}
|
||||
onColumnMoved={onColumnMoved}
|
||||
onRowDoubleClicked={handleRowDoubleClick}
|
||||
ref={tableRef}
|
||||
rowClassRules={rowClassRules}
|
||||
rowData={songsRowData}
|
||||
rowSelection="multiple"
|
||||
shouldUpdateSong
|
||||
stickyHeader
|
||||
suppressCellFocus
|
||||
suppressLoadingOverlay
|
||||
suppressRowDrag
|
||||
/>
|
||||
</Box>
|
||||
<Stack
|
||||
ref={cq.ref}
|
||||
mt="3rem"
|
||||
ref={cq.ref}
|
||||
spacing="lg"
|
||||
>
|
||||
{cq.height || cq.width ? (
|
||||
|
|
@ -498,7 +500,6 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||
.filter((c) => !c.isHidden)
|
||||
.map((carousel, index) => (
|
||||
<MemoizedSwiperGridCarousel
|
||||
key={`carousel-${carousel.uniqueId}-${index}`}
|
||||
cardRows={[
|
||||
{
|
||||
property: 'name',
|
||||
|
|
@ -529,6 +530,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
|||
data={carousel.data}
|
||||
isLoading={carousel.loading}
|
||||
itemType={LibraryItem.ALBUM}
|
||||
key={`carousel-${carousel.uniqueId}-${index}`}
|
||||
route={{
|
||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import { forwardRef, Fragment, Ref, useCallback, useMemo } from 'react';
|
||||
import { Group, Stack } from '@mantine/core';
|
||||
import { forwardRef, Fragment, Ref, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { generatePath, useParams } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { AlbumDetailResponse, LibraryItem, ServerType } from '/@/renderer/api/types';
|
||||
import { Rating, Text } from '/@/renderer/components';
|
||||
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
||||
import { LibraryHeader, useSetRating } from '/@/renderer/features/shared';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { useSongChange } from '/@/renderer/hooks/use-song-change';
|
||||
import { queryClient } from '/@/renderer/lib/react-query';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { formatDateAbsoluteUTC, formatDurationString } from '/@/renderer/utils';
|
||||
import { useSongChange } from '/@/renderer/hooks/use-song-change';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { queryClient } from '/@/renderer/lib/react-query';
|
||||
|
||||
interface AlbumDetailHeaderProps {
|
||||
background: {
|
||||
|
|
@ -121,9 +122,9 @@ export const AlbumDetailHeader = forwardRef(
|
|||
return (
|
||||
<Stack ref={cq.ref}>
|
||||
<LibraryHeader
|
||||
ref={ref}
|
||||
imageUrl={detailQuery?.data?.imageUrl}
|
||||
item={{ route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }}
|
||||
ref={ref}
|
||||
title={detailQuery?.data?.name || ''}
|
||||
{...background}
|
||||
>
|
||||
|
|
@ -139,12 +140,12 @@ export const AlbumDetailHeader = forwardRef(
|
|||
<>
|
||||
<Text $noSelect>•</Text>
|
||||
<Rating
|
||||
onChange={handleUpdateRating}
|
||||
readOnly={
|
||||
detailQuery?.isFetching ||
|
||||
updateRatingMutation.isLoading
|
||||
}
|
||||
value={detailQuery?.data?.userRating || 0}
|
||||
onChange={handleUpdateRating}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -153,17 +154,17 @@ export const AlbumDetailHeader = forwardRef(
|
|||
mah="4rem"
|
||||
spacing="md"
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
WebkitLineClamp: 2,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{detailQuery?.data?.albumArtists.map((artist) => (
|
||||
<Text
|
||||
key={`artist-${artist.id}`}
|
||||
$link
|
||||
component={Link}
|
||||
fw={600}
|
||||
key={`artist-${artist.id}`}
|
||||
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||
albumArtistId: artist.id,
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { lazy, MutableRefObject, Suspense } from 'react';
|
||||
|
||||
import { Spinner } from '/@/renderer/components';
|
||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||
import { useListContext } from '/@/renderer/context/list-context';
|
||||
|
|
@ -19,12 +21,12 @@ const AlbumListTableView = lazy(() =>
|
|||
);
|
||||
|
||||
interface AlbumListContentProps {
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount?: number;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListContentProps) => {
|
||||
export const AlbumListContent = ({ gridRef, itemCount, tableRef }: AlbumListContentProps) => {
|
||||
const { pageKey } = useListContext();
|
||||
const { display } = useListStoreByKey({ key: pageKey });
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useCallback, useMemo } from 'react';
|
|||
import { useSearchParams } from 'react-router-dom';
|
||||
import AutoSizer, { Size } from 'react-virtualized-auto-sizer';
|
||||
import { ListOnScrollProps } from 'react-window';
|
||||
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import {
|
||||
|
|
@ -19,17 +20,17 @@ import {
|
|||
} 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, useListStoreByKey } from '/@/renderer/store';
|
||||
import { CardRow, ListDisplayType } from '/@/renderer/types';
|
||||
import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite';
|
||||
|
||||
export const AlbumListGridView = ({ gridRef, itemCount }: any) => {
|
||||
const queryClient = useQueryClient();
|
||||
const server = useCurrentServer();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const { pageKey, customFilters, id } = useListContext();
|
||||
const { grid, display, filter } = useListStoreByKey<AlbumListQuery>({ key: pageKey });
|
||||
const { customFilters, id, pageKey } = useListContext();
|
||||
const { display, filter, grid } = useListStoreByKey<AlbumListQuery>({ key: pageKey });
|
||||
const { setGrid } = useListStoreActions();
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
|
@ -189,8 +190,6 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => {
|
|||
<AutoSizer>
|
||||
{({ height, width }: Size) => (
|
||||
<VirtualInfiniteGrid
|
||||
key={`album-list-${server?.id}-${display}`}
|
||||
ref={gridRef}
|
||||
cardRows={cardRows}
|
||||
display={display || ListDisplayType.CARD}
|
||||
fetchFn={fetch}
|
||||
|
|
@ -203,14 +202,16 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => {
|
|||
itemGap={grid?.itemGap ?? 10}
|
||||
itemSize={grid?.itemSize || 200}
|
||||
itemType={LibraryItem.ALBUM}
|
||||
key={`album-list-${server?.id}-${display}`}
|
||||
loading={itemCount === undefined || itemCount === null}
|
||||
minimumBatchSize={40}
|
||||
onScroll={handleGridScroll}
|
||||
ref={gridRef}
|
||||
route={{
|
||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||
slugs: [{ idProperty: 'id', slugProperty: 'albumId' }],
|
||||
}}
|
||||
width={width}
|
||||
onScroll={handleGridScroll}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { Divider, Flex, Group, Stack } from '@mantine/core';
|
||||
import { openModal } from '@mantine/modals';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
RiAddBoxFill,
|
||||
|
|
@ -14,6 +15,8 @@ import {
|
|||
RiRefreshLine,
|
||||
RiSettings3Fill,
|
||||
} from 'react-icons/ri';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import {
|
||||
AlbumListQuery,
|
||||
|
|
@ -39,7 +42,6 @@ import {
|
|||
useListStoreByKey,
|
||||
} from '/@/renderer/store';
|
||||
import { ListDisplayType, Play, TableColumn } from '/@/renderer/types';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
|
||||
const FILTERS = {
|
||||
jellyfin: [
|
||||
|
|
@ -191,7 +193,7 @@ const FILTERS = {
|
|||
};
|
||||
|
||||
interface AlbumListHeaderFiltersProps {
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount: number | undefined;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
|
@ -203,16 +205,16 @@ export const AlbumListHeaderFilters = ({
|
|||
}: AlbumListHeaderFiltersProps) => {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const { pageKey, customFilters, handlePlay } = useListContext();
|
||||
const { customFilters, handlePlay, pageKey } = useListContext();
|
||||
const server = useCurrentServer();
|
||||
const { setFilter, setTable, setGrid, setDisplayType } = useListStoreActions();
|
||||
const { display, filter, table, grid } = useListStoreByKey<AlbumListQuery>({
|
||||
const { setDisplayType, setFilter, setGrid, setTable } = useListStoreActions();
|
||||
const { display, filter, grid, table } = useListStoreByKey<AlbumListQuery>({
|
||||
filter: customFilters,
|
||||
key: pageKey,
|
||||
});
|
||||
const cq = useContainerQuery();
|
||||
|
||||
const { handleRefreshTable, handleRefreshGrid } = useListFilterRefresh({
|
||||
const { handleRefreshGrid, handleRefreshTable } = useListFilterRefresh({
|
||||
itemCount,
|
||||
itemType: LibraryItem.ALBUM,
|
||||
server,
|
||||
|
|
@ -249,12 +251,12 @@ export const AlbumListHeaderFilters = ({
|
|||
let FilterComponent;
|
||||
|
||||
switch (server?.type) {
|
||||
case ServerType.NAVIDROME:
|
||||
FilterComponent = NavidromeAlbumFilters;
|
||||
break;
|
||||
case ServerType.JELLYFIN:
|
||||
FilterComponent = JellyfinAlbumFilters;
|
||||
break;
|
||||
case ServerType.NAVIDROME:
|
||||
FilterComponent = NavidromeAlbumFilters;
|
||||
break;
|
||||
case ServerType.SUBSONIC:
|
||||
FilterComponent = SubsonicAlbumFilters;
|
||||
break;
|
||||
|
|
@ -271,9 +273,9 @@ export const AlbumListHeaderFilters = ({
|
|||
<FilterComponent
|
||||
customFilters={customFilters}
|
||||
disableArtistFilter={!!customFilters}
|
||||
onFilterChange={onFilterChange}
|
||||
pageKey={pageKey}
|
||||
serverId={server?.id}
|
||||
onFilterChange={onFilterChange}
|
||||
/>
|
||||
),
|
||||
title: 'Album Filters',
|
||||
|
|
@ -450,10 +452,10 @@ export const AlbumListHeaderFilters = ({
|
|||
<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>
|
||||
|
|
@ -462,8 +464,8 @@ export const AlbumListHeaderFilters = ({
|
|||
</DropdownMenu>
|
||||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
sortOrder={filter.sortOrder}
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filter.sortOrder}
|
||||
/>
|
||||
{server?.type === ServerType.JELLYFIN && (
|
||||
<>
|
||||
|
|
@ -489,10 +491,10 @@ export const AlbumListHeaderFilters = ({
|
|||
<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>
|
||||
|
|
@ -504,6 +506,7 @@ export const AlbumListHeaderFilters = ({
|
|||
<Divider orientation="vertical" />
|
||||
<Button
|
||||
compact
|
||||
onClick={handleOpenFiltersModal}
|
||||
size="md"
|
||||
sx={{
|
||||
svg: {
|
||||
|
|
@ -514,17 +517,16 @@ export const AlbumListHeaderFilters = ({
|
|||
label: t('common.filters', { count: 2, postProcess: 'sentenceCase' }),
|
||||
}}
|
||||
variant="subtle"
|
||||
onClick={handleOpenFiltersModal}
|
||||
>
|
||||
<RiFilterFill size="1.3rem" />
|
||||
</Button>
|
||||
<Divider orientation="vertical" />
|
||||
<Button
|
||||
compact
|
||||
onClick={handleRefresh}
|
||||
size="md"
|
||||
tooltip={{ label: t('common.refresh', { postProcess: 'sentenceCase' }) }}
|
||||
variant="subtle"
|
||||
onClick={handleRefresh}
|
||||
>
|
||||
<RiRefreshLine size="1.3rem" />
|
||||
</Button>
|
||||
|
|
@ -594,22 +596,22 @@ export const AlbumListHeaderFilters = ({
|
|||
</DropdownMenu.Label>
|
||||
<DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.CARD}
|
||||
value={ListDisplayType.CARD}
|
||||
onClick={handleSetViewType}
|
||||
value={ListDisplayType.CARD}
|
||||
>
|
||||
{t('table.config.view.card', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.POSTER}
|
||||
value={ListDisplayType.POSTER}
|
||||
onClick={handleSetViewType}
|
||||
value={ListDisplayType.POSTER}
|
||||
>
|
||||
{t('table.config.view.poster', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
$isActive={display === ListDisplayType.TABLE}
|
||||
value={ListDisplayType.TABLE}
|
||||
onClick={handleSetViewType}
|
||||
value={ListDisplayType.TABLE}
|
||||
>
|
||||
{t('table.config.view.table', { postProcess: 'sentenceCase' })}
|
||||
</DropdownMenu.Item>
|
||||
|
|
@ -665,8 +667,8 @@ export const AlbumListHeaderFilters = ({
|
|||
defaultValue={table?.columns.map(
|
||||
(column) => column.column,
|
||||
)}
|
||||
width={300}
|
||||
onChange={handleTableColumns}
|
||||
width={300}
|
||||
/>
|
||||
<Group position="apart">
|
||||
<Text>Auto Fit Columns</Text>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
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 { type ChangeEvent, type MutableRefObject, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { AlbumListQuery, LibraryItem } from '/@/renderer/api/types';
|
||||
import { PageHeader, SearchInput } from '/@/renderer/components';
|
||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||
import { AlbumListHeaderFilters } from '/@/renderer/features/albums/components/album-list-header-filters';
|
||||
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { useDisplayRefresh } from '/@/renderer/hooks/use-display-refresh';
|
||||
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>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount?: number;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
title?: string;
|
||||
|
|
@ -23,8 +25,8 @@ interface AlbumListHeaderProps {
|
|||
|
||||
export const AlbumListHeader = ({
|
||||
genreId,
|
||||
itemCount,
|
||||
gridRef,
|
||||
itemCount,
|
||||
tableRef,
|
||||
title,
|
||||
}: AlbumListHeaderProps) => {
|
||||
|
|
@ -82,8 +84,8 @@ export const AlbumListHeader = ({
|
|||
<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>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useVirtualTable } from '../../../components/virtual-table/hooks/use-virtual-table';
|
||||
|
||||
import { LibraryItem } from '/@/renderer/api/types';
|
||||
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||
import { VirtualTable } from '/@/renderer/components/virtual-table';
|
||||
|
|
@ -6,9 +7,9 @@ import { useListContext } from '/@/renderer/context/list-context';
|
|||
import { ALBUM_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
|
||||
export const AlbumListTableView = ({ tableRef, itemCount }: any) => {
|
||||
export const AlbumListTableView = ({ itemCount, tableRef }: any) => {
|
||||
const server = useCurrentServer();
|
||||
const { pageKey, customFilters, id } = useListContext();
|
||||
const { customFilters, id, pageKey } = useListContext();
|
||||
|
||||
const tableProps = useVirtualTable({
|
||||
contextMenu: ALBUM_CONTEXT_MENU_ITEMS,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { ChangeEvent, useMemo, useState } from 'react';
|
||||
import { Divider, Group, Stack } from '@mantine/core';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { ChangeEvent, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useListFilterByKey } from '../../../store/list.store';
|
||||
|
||||
import {
|
||||
AlbumArtistListSort,
|
||||
AlbumListQuery,
|
||||
|
|
@ -11,11 +13,11 @@ import {
|
|||
SortOrder,
|
||||
} from '/@/renderer/api/types';
|
||||
import { NumberInput, SpinnerIcon, Switch, Text } from '/@/renderer/components';
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { AlbumListFilter, useListStoreActions } from '/@/renderer/store';
|
||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
import { AlbumListFilter, useListStoreActions } from '/@/renderer/store';
|
||||
|
||||
interface JellyfinAlbumFiltersProps {
|
||||
customFilters?: Partial<AlbumListFilter>;
|
||||
|
|
@ -151,7 +153,7 @@ export const JellyfinAlbumFilters = ({
|
|||
}));
|
||||
}, [albumArtistListQuery?.data?.items]);
|
||||
|
||||
const handleAlbumArtistFilter = (e: string[] | null) => {
|
||||
const handleAlbumArtistFilter = (e: null | string[]) => {
|
||||
const updatedFilters = setFilter({
|
||||
customFilters,
|
||||
data: {
|
||||
|
|
@ -192,8 +194,8 @@ export const JellyfinAlbumFilters = ({
|
|||
<Text>{filter.label}</Text>
|
||||
<Switch
|
||||
checked={filter?.value || false}
|
||||
size="xs"
|
||||
onChange={filter.onChange}
|
||||
size="xs"
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
|
|
@ -205,8 +207,8 @@ export const JellyfinAlbumFilters = ({
|
|||
label={t('filter.fromYear', { postProcess: 'sentenceCase' })}
|
||||
max={2300}
|
||||
min={1700}
|
||||
required={!!filter?.maxYear}
|
||||
onChange={(e) => handleMinYearFilter(e)}
|
||||
required={!!filter?.maxYear}
|
||||
/>
|
||||
<NumberInput
|
||||
defaultValue={filter?.maxYear}
|
||||
|
|
@ -214,47 +216,47 @@ export const JellyfinAlbumFilters = ({
|
|||
label={t('filter.toYear', { postProcess: 'sentenceCase' })}
|
||||
max={2300}
|
||||
min={1700}
|
||||
required={!!filter?.minYear}
|
||||
onChange={(e) => handleMaxYearFilter(e)}
|
||||
required={!!filter?.minYear}
|
||||
/>
|
||||
</Group>
|
||||
<Group grow>
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
searchable
|
||||
data={genreList}
|
||||
defaultValue={filter.genres}
|
||||
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
|
||||
onChange={handleGenresFilter}
|
||||
searchable
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<Group grow>
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
searchable
|
||||
data={selectableAlbumArtists}
|
||||
defaultValue={filter?._custom?.jellyfin?.AlbumArtistIds?.split(',')}
|
||||
disabled={disableArtistFilter}
|
||||
label={t('entity.artist', { count: 2, postProcess: 'sentenceCase' })}
|
||||
limit={300}
|
||||
placeholder="Type to search for an artist"
|
||||
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
||||
searchValue={albumArtistSearchTerm}
|
||||
onChange={handleAlbumArtistFilter}
|
||||
onSearchChange={setAlbumArtistSearchTerm}
|
||||
placeholder="Type to search for an artist"
|
||||
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
||||
searchable
|
||||
searchValue={albumArtistSearchTerm}
|
||||
/>
|
||||
</Group>
|
||||
{tagsQuery.data?.boolTags?.length && (
|
||||
<Group grow>
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
searchable
|
||||
data={tagsQuery.data.boolTags}
|
||||
defaultValue={selectedTags}
|
||||
label={t('common.tags', { postProcess: 'sentenceCase' })}
|
||||
width={250}
|
||||
onChange={handleTagFilter}
|
||||
searchable
|
||||
width={250}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { ChangeEvent, useMemo, useState } from 'react';
|
||||
import { Divider, Group, Stack } from '@mantine/core';
|
||||
import { NumberInput, Switch, Text, SpinnerIcon } from '/@/renderer/components';
|
||||
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
||||
import { ChangeEvent, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
AlbumArtistListSort,
|
||||
AlbumListQuery,
|
||||
|
|
@ -12,9 +10,12 @@ import {
|
|||
LibraryItem,
|
||||
SortOrder,
|
||||
} from '/@/renderer/api/types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
||||
import { NumberInput, SpinnerIcon, Switch, Text } from '/@/renderer/components';
|
||||
import { SelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
||||
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
||||
|
||||
interface NavidromeAlbumFiltersProps {
|
||||
customFilters?: Partial<AlbumListFilter>;
|
||||
|
|
@ -26,8 +27,8 @@ interface NavidromeAlbumFiltersProps {
|
|||
|
||||
export const NavidromeAlbumFilters = ({
|
||||
customFilters,
|
||||
onFilterChange,
|
||||
disableArtistFilter,
|
||||
onFilterChange,
|
||||
pageKey,
|
||||
serverId,
|
||||
}: NavidromeAlbumFiltersProps) => {
|
||||
|
|
@ -52,7 +53,7 @@ export const NavidromeAlbumFilters = ({
|
|||
}));
|
||||
}, [genreListQuery.data]);
|
||||
|
||||
const handleGenresFilter = debounce((e: string | null) => {
|
||||
const handleGenresFilter = debounce((e: null | string) => {
|
||||
const updatedFilters = setFilter({
|
||||
customFilters,
|
||||
data: {
|
||||
|
|
@ -192,7 +193,7 @@ export const NavidromeAlbumFilters = ({
|
|||
}));
|
||||
}, [albumArtistListQuery?.data?.items]);
|
||||
|
||||
const handleAlbumArtistFilter = (e: string | null) => {
|
||||
const handleAlbumArtistFilter = (e: null | string) => {
|
||||
const updatedFilters = setFilter({
|
||||
data: {
|
||||
_custom: {
|
||||
|
|
@ -209,7 +210,7 @@ export const NavidromeAlbumFilters = ({
|
|||
onFilterChange(updatedFilters);
|
||||
};
|
||||
|
||||
const handleTagFilter = debounce((tag: string, e: string | null) => {
|
||||
const handleTagFilter = debounce((tag: string, e: null | string) => {
|
||||
const updatedFilters = setFilter({
|
||||
customFilters,
|
||||
data: {
|
||||
|
|
@ -254,44 +255,44 @@ export const NavidromeAlbumFilters = ({
|
|||
/>
|
||||
<SelectWithInvalidData
|
||||
clearable
|
||||
searchable
|
||||
data={genreList}
|
||||
defaultValue={filter._custom?.navidrome?.genre_id}
|
||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||
onChange={handleGenresFilter}
|
||||
searchable
|
||||
/>
|
||||
</Group>
|
||||
<Group grow>
|
||||
<SelectWithInvalidData
|
||||
clearable
|
||||
searchable
|
||||
data={selectableAlbumArtists}
|
||||
defaultValue={filter._custom?.navidrome?.artist_id}
|
||||
disabled={disableArtistFilter}
|
||||
label={t('entity.artist', { count: 1, postProcess: 'titleCase' })}
|
||||
limit={300}
|
||||
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
||||
searchValue={albumArtistSearchTerm}
|
||||
onChange={handleAlbumArtistFilter}
|
||||
onSearchChange={setAlbumArtistSearchTerm}
|
||||
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
||||
searchable
|
||||
searchValue={albumArtistSearchTerm}
|
||||
/>
|
||||
</Group>
|
||||
{tagsQuery.data?.enumTags?.length &&
|
||||
tagsQuery.data.enumTags.map((tag) => (
|
||||
<Group
|
||||
key={tag.name}
|
||||
grow
|
||||
key={tag.name}
|
||||
>
|
||||
<SelectWithInvalidData
|
||||
clearable
|
||||
searchable
|
||||
data={tag.options}
|
||||
defaultValue={
|
||||
filter._custom?.navidrome?.[tag.name] as string | undefined
|
||||
}
|
||||
label={tag.name}
|
||||
width={150}
|
||||
onChange={(value) => handleTagFilter(tag.name, value)}
|
||||
searchable
|
||||
width={150}
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Divider, Group, Stack } from '@mantine/core';
|
|||
import debounce from 'lodash/debounce';
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { AlbumListQuery, GenreListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
|
||||
import { NumberInput, Select, Switch, Text } from '/@/renderer/components';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
|
|
@ -39,7 +40,7 @@ export const SubsonicAlbumFilters = ({
|
|||
}));
|
||||
}, [genreListQuery.data]);
|
||||
|
||||
const handleGenresFilter = debounce((e: string | null) => {
|
||||
const handleGenresFilter = debounce((e: null | string) => {
|
||||
const updatedFilters = setFilter({
|
||||
data: {
|
||||
genres: e ? [e] : undefined,
|
||||
|
|
@ -68,7 +69,7 @@ export const SubsonicAlbumFilters = ({
|
|||
},
|
||||
];
|
||||
|
||||
const handleYearFilter = debounce((e: number | string, type: 'min' | 'max') => {
|
||||
const handleYearFilter = debounce((e: number | string, type: 'max' | 'min') => {
|
||||
let data: Partial<AlbumListQuery> = {};
|
||||
|
||||
if (type === 'min') {
|
||||
|
|
@ -128,12 +129,12 @@ export const SubsonicAlbumFilters = ({
|
|||
<Group grow>
|
||||
<Select
|
||||
clearable
|
||||
searchable
|
||||
data={genreList}
|
||||
defaultValue={filter.genres?.length ? filter.genres[0] : undefined}
|
||||
disabled={Boolean(filter.minYear || filter.maxYear)}
|
||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||
onChange={handleGenresFilter}
|
||||
searchable
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { createContext, useContext } from 'react';
|
||||
|
||||
import { ListKey } from '/@/renderer/store';
|
||||
|
||||
export const AlbumListContext = createContext<{ id?: string; pageKey: ListKey }>({
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '../../../store/auth.store';
|
||||
import type { AlbumDetailQuery } from '/@/renderer/api/types';
|
||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { getServerById } from '../../../store/auth.store';
|
||||
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
|
||||
export const useAlbumDetail = (args: QueryHookArgs<AlbumDetailQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import type { AlbumListQuery } from '/@/renderer/api/types';
|
||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useAlbumListCount = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import type { AlbumListQuery, AlbumListResponse } from '/@/renderer/api/types';
|
||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
|
||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import type { AlbumListQuery, AlbumListResponse } from '/@/renderer/api/types';
|
||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useAlbumList = (args: QueryHookArgs<AlbumListQuery>) => {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import { NativeScrollArea, Spinner } from '/@/renderer/components';
|
||||
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||
import { useRef } from 'react';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
||||
|
||||
import { useRef } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||
|
||||
import { LibraryItem } from '/@/renderer/api/types';
|
||||
import { NativeScrollArea, Spinner } from '/@/renderer/components';
|
||||
import { AlbumDetailContent } from '/@/renderer/features/albums/components/album-detail-content';
|
||||
import { AlbumDetailHeader } from '/@/renderer/features/albums/components/album-detail-header';
|
||||
import { useAlbumDetail } from '/@/renderer/features/albums/queries/album-detail-query';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
import { LibraryItem } from '/@/renderer/api/types';
|
||||
import { AnimatedPage, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||
import { useFastAverageColor } from '/@/renderer/hooks';
|
||||
import { useCurrentServer, useGeneralSettings } from '/@/renderer/store';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
|
||||
const AlbumDetailRoute = () => {
|
||||
const tableRef = useRef<AgGridReactType | null>(null);
|
||||
|
|
@ -49,7 +51,6 @@ const AlbumDetailRoute = () => {
|
|||
return (
|
||||
<AnimatedPage key={`album-detail-${albumId}`}>
|
||||
<NativeScrollArea
|
||||
ref={scrollAreaRef}
|
||||
pageHeaderProps={{
|
||||
backgroundColor: backgroundColor || undefined,
|
||||
children: (
|
||||
|
|
@ -63,13 +64,14 @@ const AlbumDetailRoute = () => {
|
|||
offset: 200,
|
||||
target: headerRef,
|
||||
}}
|
||||
ref={scrollAreaRef}
|
||||
>
|
||||
<AlbumDetailHeader
|
||||
ref={headerRef}
|
||||
background={{
|
||||
background,
|
||||
blur: (albumBackground && albumBackgroundBlur) || 0,
|
||||
}}
|
||||
ref={headerRef}
|
||||
/>
|
||||
<AlbumDetailContent
|
||||
background={background}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
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 { AlbumListQuery, GenreListSort, LibraryItem, SortOrder } from '/@/renderer/api/types';
|
||||
|
|
@ -21,7 +23,7 @@ import { useAlbumListCount } from '/@/renderer/features/albums/queries/album-lis
|
|||
|
||||
const AlbumListRoute = () => {
|
||||
const { t } = useTranslation();
|
||||
const gridRef = useRef<VirtualInfiniteGridRef | null>(null);
|
||||
const gridRef = useRef<null | VirtualInfiniteGridRef>(null);
|
||||
const tableRef = useRef<AgGridReactType | null>(null);
|
||||
const server = useCurrentServer();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
import { Box, Center, Group, Stack } from '@mantine/core';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Fragment } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RiErrorWarningLine, RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
|
||||
import { generatePath, useParams } from 'react-router';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { LibraryItem, SongDetailResponse } from '/@/renderer/api/types';
|
||||
import { Button, Spinner, Spoiler, Text } from '/@/renderer/components';
|
||||
import { useHandleGeneralContextMenu } from '/@/renderer/features/context-menu';
|
||||
import { SONG_ALBUM_PAGE } from '/@/renderer/features/context-menu/context-menu-items';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import {
|
||||
AnimatedPage,
|
||||
LibraryHeader,
|
||||
|
|
@ -6,27 +21,13 @@ import {
|
|||
useCreateFavorite,
|
||||
useDeleteFavorite,
|
||||
} from '/@/renderer/features/shared';
|
||||
import { Fragment } from 'react';
|
||||
import { generatePath, useParams } from 'react-router';
|
||||
import { useContainerQuery, useFastAverageColor } from '/@/renderer/hooks';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
import { LibraryItem, SongDetailResponse } from '/@/renderer/api/types';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { Stack, Group, Box, Center } from '@mantine/core';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { formatDurationString } from '/@/renderer/utils';
|
||||
import { RiErrorWarningLine, RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
|
||||
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
|
||||
import { SONG_ALBUM_PAGE } from '/@/renderer/features/context-menu/context-menu-items';
|
||||
import { useHandleGeneralContextMenu } from '/@/renderer/features/context-menu';
|
||||
import { styled } from 'styled-components';
|
||||
import { queryClient } from '/@/renderer/lib/react-query';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
import { formatDurationString } from '/@/renderer/utils';
|
||||
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
|
||||
|
||||
const DetailContainer = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -152,17 +153,17 @@ const DummyAlbumDetailRoute = () => {
|
|||
mah="4rem"
|
||||
spacing="md"
|
||||
sx={{
|
||||
overflow: 'hidden',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
WebkitLineClamp: 2,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{detailQuery?.data?.albumArtists.map((artist) => (
|
||||
<Text
|
||||
key={`artist-${artist.id}`}
|
||||
$link
|
||||
component={Link}
|
||||
fw={600}
|
||||
key={`artist-${artist.id}`}
|
||||
size="md"
|
||||
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||
albumArtistId: artist.id,
|
||||
|
|
@ -190,8 +191,8 @@ const DummyAlbumDetailRoute = () => {
|
|||
createFavoriteMutation.isLoading ||
|
||||
deleteFavoriteMutation.isLoading
|
||||
}
|
||||
variant="subtle"
|
||||
onClick={handleFavorite}
|
||||
variant="subtle"
|
||||
>
|
||||
{detailQuery?.data?.userFavorite ? (
|
||||
<RiHeartFill
|
||||
|
|
@ -204,11 +205,11 @@ const DummyAlbumDetailRoute = () => {
|
|||
</Button>
|
||||
<Button
|
||||
compact
|
||||
variant="subtle"
|
||||
onClick={(e) => {
|
||||
if (!detailQuery?.data) return;
|
||||
handleGeneralContextMenu(e, [detailQuery.data!]);
|
||||
}}
|
||||
variant="subtle"
|
||||
>
|
||||
<RiMoreFill size={20} />
|
||||
</Button>
|
||||
|
|
@ -220,9 +221,9 @@ const DummyAlbumDetailRoute = () => {
|
|||
<Group spacing="sm">
|
||||
{detailQuery?.data?.genres?.map((genre) => (
|
||||
<Button
|
||||
key={`genre-${genre.id}`}
|
||||
compact
|
||||
component={Link}
|
||||
key={`genre-${genre.id}`}
|
||||
radius={0}
|
||||
size="md"
|
||||
to={generatePath(AppRoute.LIBRARY_GENRES_SONGS, {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue