mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13: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,13 +1,14 @@
|
|||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { Divider, Group, Stack } from '@mantine/core';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/renderer/api/types';
|
||||
import { NumberInput, Switch, Text } from '/@/renderer/components';
|
||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||
|
||||
interface JellyfinSongFiltersProps {
|
||||
customFilters?: Partial<SongListFilter>;
|
||||
|
|
@ -18,8 +19,8 @@ interface JellyfinSongFiltersProps {
|
|||
|
||||
export const JellyfinSongFilters = ({
|
||||
customFilters,
|
||||
pageKey,
|
||||
onFilterChange,
|
||||
pageKey,
|
||||
serverId,
|
||||
}: JellyfinSongFiltersProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -188,28 +189,28 @@ export const JellyfinSongFilters = ({
|
|||
label={t('filter.fromYear', { postProcess: 'sentenceCase' })}
|
||||
max={2300}
|
||||
min={1700}
|
||||
required={!!filter?.minYear}
|
||||
onChange={handleMinYearFilter}
|
||||
required={!!filter?.minYear}
|
||||
/>
|
||||
<NumberInput
|
||||
defaultValue={filter?.maxYear}
|
||||
label={t('filter.toYear', { postProcess: 'sentenceCase' })}
|
||||
max={2300}
|
||||
min={1700}
|
||||
required={!!filter?.minYear}
|
||||
onChange={handleMaxYearFilter}
|
||||
required={!!filter?.minYear}
|
||||
/>
|
||||
</Group>
|
||||
{!isGenrePage && (
|
||||
<Group grow>
|
||||
<MultiSelectWithInvalidData
|
||||
clearable
|
||||
searchable
|
||||
data={genreList}
|
||||
defaultValue={selectedGenres}
|
||||
label={t('entity.genre', { count: 1, postProcess: 'sentenceCase' })}
|
||||
width={250}
|
||||
onChange={handleGenresFilter}
|
||||
searchable
|
||||
width={250}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
|
|
@ -217,12 +218,12 @@ export const JellyfinSongFilters = ({
|
|||
<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,13 +1,14 @@
|
|||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { Divider, Group, Stack } from '@mantine/core';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/renderer/api/types';
|
||||
import { NumberInput, Switch, Text } from '/@/renderer/components';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
||||
import { SelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||
|
||||
interface NavidromeSongFiltersProps {
|
||||
customFilters?: Partial<SongListFilter>;
|
||||
|
|
@ -52,7 +53,7 @@ export const NavidromeSongFilters = ({
|
|||
}));
|
||||
}, [genreListQuery.data]);
|
||||
|
||||
const handleGenresFilter = debounce((e: string | null) => {
|
||||
const handleGenresFilter = debounce((e: null | string) => {
|
||||
const updatedFilters = setFilter({
|
||||
customFilters,
|
||||
data: {
|
||||
|
|
@ -66,7 +67,7 @@ export const NavidromeSongFilters = ({
|
|||
onFilterChange(updatedFilters);
|
||||
}, 250);
|
||||
|
||||
const handleTagFilter = debounce((tag: string, e: string | null) => {
|
||||
const handleTagFilter = debounce((tag: string, e: null | string) => {
|
||||
const updatedFilters = setFilter({
|
||||
customFilters,
|
||||
data: {
|
||||
|
|
@ -134,8 +135,8 @@ export const NavidromeSongFilters = ({
|
|||
<Text>{filter.label}</Text>
|
||||
<Switch
|
||||
checked={filter?.value || false}
|
||||
size="xs"
|
||||
onChange={filter.onChange}
|
||||
size="xs"
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
|
|
@ -145,38 +146,38 @@ export const NavidromeSongFilters = ({
|
|||
label={t('common.year', { postProcess: 'titleCase' })}
|
||||
max={5000}
|
||||
min={0}
|
||||
onChange={(e) => handleYearFilter(e)}
|
||||
value={filter._custom?.navidrome?.year}
|
||||
width={50}
|
||||
onChange={(e) => handleYearFilter(e)}
|
||||
/>
|
||||
{!isGenrePage && (
|
||||
<SelectWithInvalidData
|
||||
clearable
|
||||
searchable
|
||||
data={genreList}
|
||||
defaultValue={filter.genreIds ? filter.genreIds[0] : undefined}
|
||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||
width={150}
|
||||
onChange={handleGenresFilter}
|
||||
searchable
|
||||
width={150}
|
||||
/>
|
||||
)}
|
||||
</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>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
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';
|
||||
import { useListStoreByKey } from '/@/renderer/store';
|
||||
import { ListDisplayType } from '/@/renderer/types';
|
||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||
|
||||
const SongListTableView = lazy(() =>
|
||||
import('/@/renderer/features/songs/components/song-list-table-view').then((module) => ({
|
||||
|
|
@ -19,12 +21,12 @@ const SongListGridView = lazy(() =>
|
|||
);
|
||||
|
||||
interface SongListContentProps {
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount?: number;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const SongListContent = ({ itemCount, gridRef, tableRef }: SongListContentProps) => {
|
||||
export const SongListContent = ({ gridRef, itemCount, tableRef }: SongListContentProps) => {
|
||||
const { pageKey } = useListContext();
|
||||
const { display } = useListStoreByKey({ key: pageKey });
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { MutableRefObject, useCallback, useEffect, 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 {
|
||||
|
|
@ -20,14 +21,14 @@ 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';
|
||||
import { useEventStore } from '/@/renderer/store/event.store';
|
||||
import { CardRow, ListDisplayType } from '/@/renderer/types';
|
||||
|
||||
interface SongListGridViewProps {
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount?: number;
|
||||
}
|
||||
|
||||
|
|
@ -35,8 +36,8 @@ export const SongListGridView = ({ gridRef, itemCount }: SongListGridViewProps)
|
|||
const queryClient = useQueryClient();
|
||||
const server = useCurrentServer();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const { pageKey, customFilters, id } = useListContext();
|
||||
const { grid, display, filter } = useListStoreByKey<SongListQuery>({ key: pageKey });
|
||||
const { customFilters, id, pageKey } = useListContext();
|
||||
const { display, filter, grid } = useListStoreByKey<SongListQuery>({ key: pageKey });
|
||||
const { setGrid } = useListStoreActions();
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
|
@ -201,8 +202,6 @@ export const SongListGridView = ({ gridRef, itemCount }: SongListGridViewProps)
|
|||
<AutoSizer>
|
||||
{({ height, width }: Size) => (
|
||||
<VirtualInfiniteGrid
|
||||
key={`song-list-${server?.id}-${display}`}
|
||||
ref={gridRef}
|
||||
cardRows={cardRows}
|
||||
display={display || ListDisplayType.CARD}
|
||||
fetchFn={fetch}
|
||||
|
|
@ -215,14 +214,16 @@ export const SongListGridView = ({ gridRef, itemCount }: SongListGridViewProps)
|
|||
itemGap={grid?.itemGap ?? 10}
|
||||
itemSize={grid?.itemSize || 200}
|
||||
itemType={LibraryItem.SONG}
|
||||
key={`song-list-${server?.id}-${display}`}
|
||||
loading={itemCount === undefined || itemCount === null}
|
||||
minimumBatchSize={40}
|
||||
onScroll={handleGridScroll}
|
||||
ref={gridRef}
|
||||
route={{
|
||||
route: AppRoute.LIBRARY_ALBUMS_DETAIL,
|
||||
slugs: [{ idProperty: 'albumId', slugProperty: 'albumId' }],
|
||||
}}
|
||||
width={width}
|
||||
onScroll={handleGridScroll}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
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 { ChangeEvent, MouseEvent, MutableRefObject, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
RiAddBoxFill,
|
||||
|
|
@ -13,7 +14,10 @@ import {
|
|||
RiRefreshLine,
|
||||
RiSettings3Fill,
|
||||
} from 'react-icons/ri';
|
||||
|
||||
import { useListStoreByKey } from '../../../store/list.store';
|
||||
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import {
|
||||
LibraryItem,
|
||||
|
|
@ -29,13 +33,12 @@ import { useListContext } from '/@/renderer/context/list-context';
|
|||
import { OrderToggleButton, useMusicFolders } from '/@/renderer/features/shared';
|
||||
import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jellyfin-song-filters';
|
||||
import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters';
|
||||
import { SubsonicSongFilters } from '/@/renderer/features/songs/components/subsonic-song-filter';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { useListFilterRefresh } from '/@/renderer/hooks/use-list-filter-refresh';
|
||||
import { queryClient } from '/@/renderer/lib/react-query';
|
||||
import { SongListFilter, useCurrentServer, useListStoreActions } from '/@/renderer/store';
|
||||
import { ListDisplayType, Play, TableColumn } from '/@/renderer/types';
|
||||
import i18n from '/@/i18n/i18n';
|
||||
import { SubsonicSongFilters } from '/@/renderer/features/songs/components/subsonic-song-filter';
|
||||
|
||||
const FILTERS = {
|
||||
jellyfin: [
|
||||
|
|
@ -182,7 +185,7 @@ const FILTERS = {
|
|||
};
|
||||
|
||||
interface SongListHeaderFiltersProps {
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount?: number;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
|
@ -194,16 +197,16 @@ export const SongListHeaderFilters = ({
|
|||
}: SongListHeaderFiltersProps) => {
|
||||
const { t } = useTranslation();
|
||||
const server = useCurrentServer();
|
||||
const { pageKey, handlePlay, customFilters } = useListContext();
|
||||
const { display, table, filter, grid } = useListStoreByKey<SongListQuery>({
|
||||
const { customFilters, handlePlay, pageKey } = useListContext();
|
||||
const { display, filter, grid, table } = useListStoreByKey<SongListQuery>({
|
||||
filter: customFilters,
|
||||
key: pageKey,
|
||||
});
|
||||
|
||||
const { setFilter, setGrid, setTable, setTablePagination, setDisplayType } =
|
||||
const { setDisplayType, setFilter, setGrid, setTable, setTablePagination } =
|
||||
useListStoreActions();
|
||||
|
||||
const { handleRefreshTable, handleRefreshGrid } = useListFilterRefresh({
|
||||
const { handleRefreshGrid, handleRefreshTable } = useListFilterRefresh({
|
||||
itemCount,
|
||||
itemType: LibraryItem.SONG,
|
||||
server,
|
||||
|
|
@ -416,12 +419,12 @@ export const SongListHeaderFilters = ({
|
|||
let FilterComponent;
|
||||
|
||||
switch (server?.type) {
|
||||
case ServerType.NAVIDROME:
|
||||
FilterComponent = NavidromeSongFilters;
|
||||
break;
|
||||
case ServerType.JELLYFIN:
|
||||
FilterComponent = JellyfinSongFilters;
|
||||
break;
|
||||
case ServerType.NAVIDROME:
|
||||
FilterComponent = NavidromeSongFilters;
|
||||
break;
|
||||
case ServerType.SUBSONIC:
|
||||
FilterComponent = SubsonicSongFilters;
|
||||
break;
|
||||
|
|
@ -435,9 +438,9 @@ export const SongListHeaderFilters = ({
|
|||
children: (
|
||||
<FilterComponent
|
||||
customFilters={customFilters}
|
||||
onFilterChange={onFilterChange}
|
||||
pageKey={pageKey}
|
||||
serverId={server?.id}
|
||||
onFilterChange={onFilterChange}
|
||||
/>
|
||||
),
|
||||
title: 'Song Filters',
|
||||
|
|
@ -493,10 +496,10 @@ export const SongListHeaderFilters = ({
|
|||
<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>
|
||||
|
|
@ -507,8 +510,8 @@ export const SongListHeaderFilters = ({
|
|||
<>
|
||||
<Divider orientation="vertical" />
|
||||
<OrderToggleButton
|
||||
sortOrder={filter.sortOrder}
|
||||
onToggle={handleToggleSortOrder}
|
||||
sortOrder={filter.sortOrder}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -536,10 +539,10 @@ export const SongListHeaderFilters = ({
|
|||
<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>
|
||||
|
|
@ -551,6 +554,7 @@ export const SongListHeaderFilters = ({
|
|||
<Divider orientation="vertical" />
|
||||
<Button
|
||||
compact
|
||||
onClick={handleOpenFiltersModal}
|
||||
size="md"
|
||||
sx={{
|
||||
svg: {
|
||||
|
|
@ -559,17 +563,16 @@ export const SongListHeaderFilters = ({
|
|||
}}
|
||||
tooltip={{ label: t('common.filters', { postProcess: 'titleCase' }) }}
|
||||
variant="subtle"
|
||||
onClick={handleOpenFiltersModal}
|
||||
>
|
||||
<RiFilterFill size="1.3rem" />
|
||||
</Button>
|
||||
<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>
|
||||
|
|
@ -637,22 +640,22 @@ export const SongListHeaderFilters = ({
|
|||
</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>
|
||||
|
|
@ -707,8 +710,8 @@ export const SongListHeaderFilters = ({
|
|||
clearable
|
||||
data={SONG_TABLE_COLUMNS}
|
||||
defaultValue={table?.columns.map((column) => column.column)}
|
||||
width={300}
|
||||
onChange={handleTableColumns}
|
||||
width={300}
|
||||
/>
|
||||
<Group position="apart">
|
||||
<Text>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
import { ChangeEvent, MutableRefObject, useEffect, useRef } 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 { ChangeEvent, MutableRefObject, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { LibraryItem, SongListQuery } from '/@/renderer/api/types';
|
||||
import { PageHeader, SearchInput } from '/@/renderer/components';
|
||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||
import { FilterBar, LibraryHeaderBar } from '/@/renderer/features/shared';
|
||||
import { SongListHeaderFilters } from '/@/renderer/features/songs/components/song-list-header-filters';
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { useDisplayRefresh } from '/@/renderer/hooks/use-display-refresh';
|
||||
import { SongListFilter, useCurrentServer } from '/@/renderer/store';
|
||||
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
|
||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||
import { useDisplayRefresh } from '/@/renderer/hooks/use-display-refresh';
|
||||
|
||||
interface SongListHeaderProps {
|
||||
genreId?: string;
|
||||
gridRef: MutableRefObject<VirtualInfiniteGridRef | null>;
|
||||
gridRef: MutableRefObject<null | VirtualInfiniteGridRef>;
|
||||
itemCount?: number;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
title?: string;
|
||||
|
|
@ -24,9 +26,9 @@ interface SongListHeaderProps {
|
|||
export const SongListHeader = ({
|
||||
genreId,
|
||||
gridRef,
|
||||
title,
|
||||
itemCount,
|
||||
tableRef,
|
||||
title,
|
||||
}: SongListHeaderProps) => {
|
||||
const { t } = useTranslation();
|
||||
const server = useCurrentServer();
|
||||
|
|
@ -90,8 +92,8 @@ export const SongListHeader = ({
|
|||
<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,6 +1,8 @@
|
|||
import { RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
|
||||
import { RowDoubleClickedEvent } from '@ag-grid-community/core';
|
||||
import { MutableRefObject } from 'react';
|
||||
|
||||
import { LibraryItem, QueueSong, SongListQuery } from '/@/renderer/api/types';
|
||||
import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid';
|
||||
import { VirtualTable } from '/@/renderer/components/virtual-table';
|
||||
|
|
@ -21,9 +23,9 @@ interface SongListTableViewProps {
|
|||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const SongListTableView = ({ tableRef, itemCount }: SongListTableViewProps) => {
|
||||
export const SongListTableView = ({ itemCount, tableRef }: SongListTableViewProps) => {
|
||||
const server = useCurrentServer();
|
||||
const { pageKey, id, handlePlay, customFilters } = useListContext();
|
||||
const { customFilters, handlePlay, id, pageKey } = useListContext();
|
||||
const isFocused = useAppFocus();
|
||||
const currentSong = useCurrentSong();
|
||||
const status = useCurrentStatus();
|
||||
|
|
@ -56,15 +58,15 @@ export const SongListTableView = ({ tableRef, itemCount }: SongListTableViewProp
|
|||
key={`table-${tableProps.rowHeight}-${server?.id}`}
|
||||
ref={tableRef}
|
||||
{...tableProps}
|
||||
shouldUpdateSong
|
||||
context={{
|
||||
...tableProps.context,
|
||||
currentSong,
|
||||
isFocused,
|
||||
status,
|
||||
}}
|
||||
rowClassRules={rowClassRules}
|
||||
onRowDoubleClicked={handleRowDoubleClick}
|
||||
rowClassRules={rowClassRules}
|
||||
shouldUpdateSong
|
||||
/>
|
||||
</VirtualGridAutoSizerContainer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { Divider, Group, Stack } from '@mantine/core';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { ChangeEvent, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/renderer/api/types';
|
||||
import { Select, Switch, Text } from '/@/renderer/components';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface SubsonicSongFiltersProps {
|
||||
customFilters?: Partial<SongListFilter>;
|
||||
|
|
@ -43,7 +44,7 @@ export const SubsonicSongFilters = ({
|
|||
}));
|
||||
}, [genreListQuery.data]);
|
||||
|
||||
const handleGenresFilter = debounce((e: string | null) => {
|
||||
const handleGenresFilter = debounce((e: null | string) => {
|
||||
const updatedFilters = setFilter({
|
||||
customFilters,
|
||||
data: {
|
||||
|
|
@ -87,8 +88,8 @@ export const SubsonicSongFilters = ({
|
|||
<Switch
|
||||
checked={filter?.value || false}
|
||||
disabled={filter.disabled}
|
||||
size="xs"
|
||||
onChange={filter.onChange}
|
||||
size="xs"
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
|
|
@ -97,13 +98,13 @@ export const SubsonicSongFilters = ({
|
|||
{!isGenrePage && (
|
||||
<Select
|
||||
clearable
|
||||
searchable
|
||||
data={genreList}
|
||||
defaultValue={filter.genreIds ? filter.genreIds[0] : undefined}
|
||||
disabled={!!filter.searchTerm}
|
||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||
width={150}
|
||||
onChange={handleGenresFilter}
|
||||
searchable
|
||||
width={150}
|
||||
/>
|
||||
)}
|
||||
</Group>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '/@/renderer/api';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import type { SongListQuery } 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 useSongListCount = (args: QueryHookArgs<SongListQuery>) => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import type { SongListQuery } from '/@/renderer/api/types';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
import type { QueryHookArgs } from '/@/renderer/lib/react-query';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { controller } from '/@/renderer/api/controller';
|
||||
import { queryKeys } from '/@/renderer/api/query-keys';
|
||||
import { getServerById } from '/@/renderer/store';
|
||||
|
||||
export const useSongList = (args: QueryHookArgs<SongListQuery>, imageSize?: number) => {
|
||||
const { query, options, serverId } = args || {};
|
||||
const { options, query, serverId } = args || {};
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
|
|
|
|||
|
|
@ -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 { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/renderer/api/types';
|
||||
import { ListContext } from '/@/renderer/context/list-context';
|
||||
import { useGenreList } from '/@/renderer/features/genres';
|
||||
|
|
@ -10,15 +12,15 @@ import { usePlayQueueAdd } from '/@/renderer/features/player';
|
|||
import { AnimatedPage } from '/@/renderer/features/shared';
|
||||
import { SongListContent } from '/@/renderer/features/songs/components/song-list-content';
|
||||
import { SongListHeader } from '/@/renderer/features/songs/components/song-list-header';
|
||||
import { useSongListCount } from '/@/renderer/features/songs/queries/song-list-count-query';
|
||||
import { useCurrentServer, useListFilterByKey } from '/@/renderer/store';
|
||||
import { Play } from '/@/renderer/types';
|
||||
import { sentenceCase, titleCase } from '/@/renderer/utils';
|
||||
import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid';
|
||||
import { useSongListCount } from '/@/renderer/features/songs/queries/song-list-count-query';
|
||||
|
||||
const TrackListRoute = () => {
|
||||
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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue