mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
reimplement search page
This commit is contained in:
@@ -1,93 +1,106 @@
|
|||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
import { RowDoubleClickedEvent } from '@ag-grid-community/core';
|
|
||||||
import { MutableRefObject } from 'react';
|
|
||||||
import { generatePath, useNavigate } from 'react-router';
|
|
||||||
import { useParams, useSearchParams } from 'react-router';
|
import { useParams, useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles';
|
|
||||||
import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table';
|
|
||||||
import {
|
import {
|
||||||
ALBUM_CONTEXT_MENU_ITEMS,
|
AlbumListView,
|
||||||
ARTIST_CONTEXT_MENU_ITEMS,
|
OverrideAlbumListQuery,
|
||||||
SONG_CONTEXT_MENU_ITEMS,
|
} from '/@/renderer/features/albums/components/album-list-content';
|
||||||
} from '/@/renderer/features/context-menu/context-menu-items';
|
import {
|
||||||
import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add';
|
AlbumArtistListView,
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
OverrideAlbumArtistListQuery,
|
||||||
import { useCurrentServer, useListStoreByKey, usePlayButtonBehavior } from '/@/renderer/store';
|
} from '/@/renderer/features/artists/components/album-artist-list-content';
|
||||||
import { LibraryItem, QueueSong, SongListQuery } from '/@/shared/types/domain-types';
|
import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
|
||||||
|
import {
|
||||||
|
OverrideSongListQuery,
|
||||||
|
SongListView,
|
||||||
|
} from '/@/renderer/features/songs/components/song-list-content';
|
||||||
|
import { useListSettings } from '/@/renderer/store';
|
||||||
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
|
import {
|
||||||
|
AlbumArtistListSort,
|
||||||
|
AlbumListSort,
|
||||||
|
LibraryItem,
|
||||||
|
SongListSort,
|
||||||
|
SortOrder,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface SearchContentProps {
|
export const SearchContent = () => {
|
||||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SearchContent = ({ tableRef }: SearchContentProps) => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const server = useCurrentServer();
|
|
||||||
const { itemType } = useParams() as { itemType: LibraryItem };
|
const { itemType } = useParams() as { itemType: LibraryItem };
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const pageKey = itemType;
|
|
||||||
const { filter } = useListStoreByKey({
|
|
||||||
filter: { searchTerm: searchParams.get('query') || '' },
|
|
||||||
key: itemType,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
return (
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
<AnimatedPage>
|
||||||
|
<Suspense fallback={<Spinner container />}>
|
||||||
const contextMenuItems = () => {
|
{itemType === LibraryItem.ALBUM && <AlbumSearch />}
|
||||||
switch (itemType) {
|
{itemType === LibraryItem.SONG && <SongSearch />}
|
||||||
case LibraryItem.ALBUM:
|
{itemType === LibraryItem.ALBUM_ARTIST && <ArtistSearch />}
|
||||||
return ALBUM_CONTEXT_MENU_ITEMS;
|
</Suspense>
|
||||||
case LibraryItem.ALBUM_ARTIST:
|
</AnimatedPage>
|
||||||
return ARTIST_CONTEXT_MENU_ITEMS;
|
);
|
||||||
case LibraryItem.SONG:
|
};
|
||||||
return SONG_CONTEXT_MENU_ITEMS;
|
|
||||||
default:
|
const AlbumSearch = () => {
|
||||||
return [];
|
const { display, grid, itemsPerPage, pagination, table } = useListSettings(ItemListKey.ALBUM);
|
||||||
}
|
const [searchParams] = useSearchParams();
|
||||||
};
|
|
||||||
|
const albumQuery: OverrideAlbumListQuery = {
|
||||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
|
searchTerm: searchParams.get('query') || '',
|
||||||
if (!e.data) return;
|
sortBy: AlbumListSort.NAME,
|
||||||
switch (itemType) {
|
sortOrder: SortOrder.ASC,
|
||||||
case LibraryItem.ALBUM:
|
};
|
||||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
|
|
||||||
break;
|
return (
|
||||||
case LibraryItem.ALBUM_ARTIST:
|
<AlbumListView
|
||||||
navigate(
|
display={display}
|
||||||
generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
grid={grid}
|
||||||
albumArtistId: e.data.id,
|
itemsPerPage={itemsPerPage}
|
||||||
}),
|
overrideQuery={albumQuery}
|
||||||
);
|
pagination={pagination}
|
||||||
break;
|
table={table}
|
||||||
case LibraryItem.SONG:
|
/>
|
||||||
handlePlayQueueAdd?.({
|
);
|
||||||
byItemType: {
|
};
|
||||||
id: [],
|
|
||||||
type: LibraryItem.SONG,
|
const SongSearch = () => {
|
||||||
},
|
const { display, grid, itemsPerPage, pagination, table } = useListSettings(ItemListKey.SONG);
|
||||||
initialSongId: e.data.id,
|
const [searchParams] = useSearchParams();
|
||||||
playType: playButtonBehavior,
|
|
||||||
query: {
|
const songQuery: OverrideSongListQuery = {
|
||||||
startIndex: 0,
|
searchTerm: searchParams.get('query') || '',
|
||||||
...filter,
|
sortBy: SongListSort.NAME,
|
||||||
},
|
sortOrder: SortOrder.ASC,
|
||||||
});
|
};
|
||||||
break;
|
|
||||||
}
|
return (
|
||||||
};
|
<SongListView
|
||||||
|
display={display}
|
||||||
const { rowClassRules } = useCurrentSongRowStyles({ tableRef });
|
grid={grid}
|
||||||
|
itemsPerPage={itemsPerPage}
|
||||||
const tableProps = useVirtualTable<SongListQuery>({
|
overrideQuery={songQuery}
|
||||||
contextMenu: contextMenuItems(),
|
pagination={pagination}
|
||||||
customFilters: filter,
|
table={table}
|
||||||
itemType,
|
/>
|
||||||
pageKey,
|
);
|
||||||
server,
|
};
|
||||||
tableRef,
|
|
||||||
});
|
const ArtistSearch = () => {
|
||||||
|
const { display, grid, itemsPerPage, pagination, table } = useListSettings(ItemListKey.ARTIST);
|
||||||
return null;
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
|
const albumArtistQuery: OverrideAlbumArtistListQuery = {
|
||||||
|
searchTerm: searchParams.get('query') || '',
|
||||||
|
sortBy: AlbumArtistListSort.NAME,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlbumArtistListView
|
||||||
|
display={display}
|
||||||
|
grid={grid}
|
||||||
|
itemsPerPage={itemsPerPage}
|
||||||
|
overrideQuery={albumArtistQuery}
|
||||||
|
pagination={pagination}
|
||||||
|
table={table}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,54 +1,53 @@
|
|||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
|
||||||
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { ChangeEvent, MutableRefObject } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, Link, useParams, useSearchParams } from 'react-router';
|
import { generatePath, Link, useParams, useSearchParams } from 'react-router';
|
||||||
|
|
||||||
|
import { ALBUM_ARTIST_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||||
import { PageHeader } from '/@/renderer/components/page-header/page-header';
|
import { PageHeader } from '/@/renderer/components/page-header/page-header';
|
||||||
|
import { ALBUM_TABLE_COLUMNS, SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table';
|
||||||
import { FilterBar } from '/@/renderer/features/shared/components/filter-bar';
|
import { FilterBar } from '/@/renderer/features/shared/components/filter-bar';
|
||||||
import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar';
|
import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar';
|
||||||
|
import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { SearchInput } from '/@/renderer/features/shared/components/search-input';
|
import { SearchInput } from '/@/renderer/features/shared/components/search-input';
|
||||||
import { useContainerQuery } from '/@/renderer/hooks';
|
import { useContainerQuery } from '/@/renderer/hooks';
|
||||||
import { useListFilterRefresh } from '/@/renderer/hooks/use-list-filter-refresh';
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer, useListStoreByKey } from '/@/renderer/store';
|
import { Button, ButtonGroup } from '/@/shared/components/button/button';
|
||||||
import { Button } from '/@/shared/components/button/button';
|
|
||||||
import { Flex } from '/@/shared/components/flex/flex';
|
import { Flex } from '/@/shared/components/flex/flex';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import {
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
AlbumArtistListQuery,
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
AlbumListQuery,
|
|
||||||
LibraryItem,
|
|
||||||
SongListQuery,
|
|
||||||
} from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
interface SearchHeaderProps {
|
interface SearchHeaderProps {
|
||||||
navigationId: string;
|
navigationId: string;
|
||||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SearchHeader = ({ navigationId, tableRef }: SearchHeaderProps) => {
|
export const SearchHeader = ({ navigationId }: SearchHeaderProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { itemType } = useParams() as { itemType: LibraryItem };
|
const { itemType } = useParams() as { itemType: LibraryItem };
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const cq = useContainerQuery();
|
const cq = useContainerQuery();
|
||||||
const server = useCurrentServer();
|
|
||||||
const { filter } = useListStoreByKey<AlbumArtistListQuery | AlbumListQuery | SongListQuery>({
|
|
||||||
key: itemType,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { handleRefreshTable } = useListFilterRefresh({
|
|
||||||
itemType,
|
|
||||||
server,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
|
const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearchParams({ query: e.target.value }, { replace: true, state: { navigationId } });
|
setSearchParams({ query: e.target.value }, { replace: true, state: { navigationId } });
|
||||||
handleRefreshTable(tableRef, { ...filter, searchTerm: e.target.value });
|
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
|
const listConfigMenuProps = {
|
||||||
|
[LibraryItem.ALBUM]: {
|
||||||
|
listKey: ItemListKey.ALBUM,
|
||||||
|
tableColumnsData: ALBUM_TABLE_COLUMNS,
|
||||||
|
},
|
||||||
|
[LibraryItem.ALBUM_ARTIST]: {
|
||||||
|
listKey: ItemListKey.ALBUM_ARTIST,
|
||||||
|
tableColumnsData: ALBUM_ARTIST_TABLE_COLUMNS,
|
||||||
|
},
|
||||||
|
[LibraryItem.SONG]: {
|
||||||
|
listKey: ItemListKey.SONG,
|
||||||
|
tableColumnsData: SONG_TABLE_COLUMNS,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap={0} ref={cq.ref}>
|
<Stack gap={0} ref={cq.ref}>
|
||||||
<PageHeader>
|
<PageHeader>
|
||||||
@@ -65,54 +64,59 @@ export const SearchHeader = ({ navigationId, tableRef }: SearchHeaderProps) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<FilterBar>
|
<FilterBar>
|
||||||
<Group>
|
<Flex justify="space-between" w="100%">
|
||||||
<Button
|
<ButtonGroup>
|
||||||
component={Link}
|
<Button
|
||||||
fw={600}
|
component={Link}
|
||||||
replace
|
fw={600}
|
||||||
size="compact-md"
|
replace
|
||||||
state={{ navigationId }}
|
size="compact-md"
|
||||||
to={{
|
state={{ navigationId }}
|
||||||
pathname: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.SONG }),
|
to={{
|
||||||
search: searchParams.toString(),
|
pathname: generatePath(AppRoute.SEARCH, {
|
||||||
}}
|
itemType: LibraryItem.SONG,
|
||||||
variant={itemType === LibraryItem.SONG ? 'filled' : 'subtle'}
|
}),
|
||||||
>
|
search: searchParams.toString(),
|
||||||
{t('entity.track_other', { postProcess: 'sentenceCase' })}
|
}}
|
||||||
</Button>
|
variant={itemType === LibraryItem.SONG ? 'filled' : 'default'}
|
||||||
<Button
|
>
|
||||||
component={Link}
|
{t('entity.track_other', { postProcess: 'sentenceCase' })}
|
||||||
fw={600}
|
</Button>
|
||||||
replace
|
<Button
|
||||||
size="compact-md"
|
component={Link}
|
||||||
state={{ navigationId }}
|
fw={600}
|
||||||
to={{
|
replace
|
||||||
pathname: generatePath(AppRoute.SEARCH, {
|
size="compact-md"
|
||||||
itemType: LibraryItem.ALBUM,
|
state={{ navigationId }}
|
||||||
}),
|
to={{
|
||||||
search: searchParams.toString(),
|
pathname: generatePath(AppRoute.SEARCH, {
|
||||||
}}
|
itemType: LibraryItem.ALBUM,
|
||||||
variant={itemType === LibraryItem.ALBUM ? 'filled' : 'subtle'}
|
}),
|
||||||
>
|
search: searchParams.toString(),
|
||||||
{t('entity.album_other', { postProcess: 'sentenceCase' })}
|
}}
|
||||||
</Button>
|
variant={itemType === LibraryItem.ALBUM ? 'filled' : 'default'}
|
||||||
<Button
|
>
|
||||||
component={Link}
|
{t('entity.album_other', { postProcess: 'sentenceCase' })}
|
||||||
fw={600}
|
</Button>
|
||||||
replace
|
<Button
|
||||||
size="compact-md"
|
component={Link}
|
||||||
state={{ navigationId }}
|
fw={600}
|
||||||
to={{
|
replace
|
||||||
pathname: generatePath(AppRoute.SEARCH, {
|
size="compact-md"
|
||||||
itemType: LibraryItem.ALBUM_ARTIST,
|
state={{ navigationId }}
|
||||||
}),
|
to={{
|
||||||
search: searchParams.toString(),
|
pathname: generatePath(AppRoute.SEARCH, {
|
||||||
}}
|
itemType: LibraryItem.ALBUM_ARTIST,
|
||||||
variant={itemType === LibraryItem.ALBUM_ARTIST ? 'filled' : 'subtle'}
|
}),
|
||||||
>
|
search: searchParams.toString(),
|
||||||
{t('entity.artist_other', { postProcess: 'sentenceCase' })}
|
}}
|
||||||
</Button>
|
variant={itemType === LibraryItem.ALBUM_ARTIST ? 'filled' : 'default'}
|
||||||
</Group>
|
>
|
||||||
|
{t('entity.artist_other', { postProcess: 'sentenceCase' })}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
<ListConfigMenu {...listConfigMenuProps[itemType]} />
|
||||||
|
</Flex>
|
||||||
</FilterBar>
|
</FilterBar>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
import { useId } from 'react';
|
||||||
|
|
||||||
import { useId, useRef } from 'react';
|
|
||||||
import { useLocation, useParams } from 'react-router';
|
import { useLocation, useParams } from 'react-router';
|
||||||
|
|
||||||
import { SearchContent } from '/@/renderer/features/search/components/search-content';
|
import { SearchContent } from '/@/renderer/features/search/components/search-content';
|
||||||
@@ -12,12 +10,11 @@ const SearchRoute = () => {
|
|||||||
const localNavigationId = useId();
|
const localNavigationId = useId();
|
||||||
const navigationId = locationState?.navigationId || localNavigationId;
|
const navigationId = locationState?.navigationId || localNavigationId;
|
||||||
const { itemType } = useParams() as { itemType: string };
|
const { itemType } = useParams() as { itemType: string };
|
||||||
const tableRef = useRef<AgGridReactType | null>(null);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedPage key={`search-${navigationId}`}>
|
<AnimatedPage key={`search-${navigationId}`}>
|
||||||
<SearchHeader navigationId={navigationId} tableRef={tableRef} />
|
<SearchHeader navigationId={navigationId} />
|
||||||
<SearchContent key={`page-${itemType}`} tableRef={tableRef} />
|
<SearchContent key={`page-${itemType}`} />
|
||||||
</AnimatedPage>
|
</AnimatedPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user