mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
Lint all files
This commit is contained in:
parent
22af76b4d6
commit
30e52ebb54
334 changed files with 76519 additions and 75932 deletions
|
|
@ -18,249 +18,261 @@ import { LibraryItem } from '/@/renderer/api/types';
|
|||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
|
||||
interface CommandPaletteProps {
|
||||
modalProps: typeof useDisclosure['arguments'];
|
||||
modalProps: typeof useDisclosure['arguments'];
|
||||
}
|
||||
|
||||
const CustomModal = styled(Modal)`
|
||||
& .mantine-Modal-header {
|
||||
display: none;
|
||||
}
|
||||
& .mantine-Modal-header {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||
const navigate = useNavigate();
|
||||
const server = useCurrentServer();
|
||||
const [value, setValue] = useState('');
|
||||
const [query, setQuery] = useState('');
|
||||
const [debouncedQuery] = useDebouncedValue(query, 400);
|
||||
const [pages, setPages] = useState<CommandPalettePages[]>([CommandPalettePages.HOME]);
|
||||
const activePage = pages[pages.length - 1];
|
||||
const isHome = activePage === CommandPalettePages.HOME;
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const navigate = useNavigate();
|
||||
const server = useCurrentServer();
|
||||
const [value, setValue] = useState('');
|
||||
const [query, setQuery] = useState('');
|
||||
const [debouncedQuery] = useDebouncedValue(query, 400);
|
||||
const [pages, setPages] = useState<CommandPalettePages[]>([CommandPalettePages.HOME]);
|
||||
const activePage = pages[pages.length - 1];
|
||||
const isHome = activePage === CommandPalettePages.HOME;
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const popPage = useCallback(() => {
|
||||
setPages((pages) => {
|
||||
const x = [...pages];
|
||||
x.splice(-1, 1);
|
||||
return x;
|
||||
const popPage = useCallback(() => {
|
||||
setPages((pages) => {
|
||||
const x = [...pages];
|
||||
x.splice(-1, 1);
|
||||
return x;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { data, isLoading } = useSearch({
|
||||
options: { enabled: isHome && debouncedQuery !== '' && query !== '' },
|
||||
query: {
|
||||
albumArtistLimit: 4,
|
||||
albumArtistStartIndex: 0,
|
||||
albumLimit: 4,
|
||||
albumStartIndex: 0,
|
||||
query: debouncedQuery,
|
||||
songLimit: 4,
|
||||
songStartIndex: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { data, isLoading } = useSearch({
|
||||
options: { enabled: isHome && debouncedQuery !== '' && query !== '' },
|
||||
query: {
|
||||
albumArtistLimit: 4,
|
||||
albumArtistStartIndex: 0,
|
||||
albumLimit: 4,
|
||||
albumStartIndex: 0,
|
||||
query: debouncedQuery,
|
||||
songLimit: 4,
|
||||
songStartIndex: 0,
|
||||
},
|
||||
serverId: server?.id,
|
||||
});
|
||||
const showAlbumGroup = isHome && Boolean(query && data && data?.albums?.length > 0);
|
||||
const showArtistGroup = isHome && Boolean(query && data && data?.albumArtists?.length > 0);
|
||||
const showTrackGroup = isHome && Boolean(query && data && data?.songs?.length > 0);
|
||||
|
||||
const showAlbumGroup = isHome && Boolean(query && data && data?.albums?.length > 0);
|
||||
const showArtistGroup = isHome && Boolean(query && data && data?.albumArtists?.length > 0);
|
||||
const showTrackGroup = isHome && Boolean(query && data && data?.songs?.length > 0);
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
|
||||
return (
|
||||
<CustomModal
|
||||
{...modalProps}
|
||||
centered
|
||||
handlers={{
|
||||
...modalProps.handlers,
|
||||
close: () => {
|
||||
if (isHome) {
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
} else {
|
||||
popPage();
|
||||
}
|
||||
},
|
||||
toggle: () => {
|
||||
if (isHome) {
|
||||
modalProps.handlers.toggle();
|
||||
setQuery('');
|
||||
} else {
|
||||
popPage();
|
||||
}
|
||||
},
|
||||
}}
|
||||
scrollAreaComponent={ScrollArea.Autosize}
|
||||
size="lg"
|
||||
>
|
||||
<Group
|
||||
mb="1rem"
|
||||
spacing="sm"
|
||||
>
|
||||
{pages.map((page, index) => (
|
||||
<Fragment key={page}>
|
||||
{index > 0 && ' > '}
|
||||
<Button
|
||||
compact
|
||||
disabled
|
||||
variant="default"
|
||||
>
|
||||
{page?.toLocaleUpperCase()}
|
||||
</Button>
|
||||
</Fragment>
|
||||
))}
|
||||
</Group>
|
||||
<Command
|
||||
filter={(value, search) => {
|
||||
if (value.includes(search)) return 1;
|
||||
if (value.includes('search')) return 1;
|
||||
return 0;
|
||||
}}
|
||||
label="Global Command Menu"
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
>
|
||||
<TextInput
|
||||
ref={searchInputRef}
|
||||
data-autofocus
|
||||
icon={<RiSearchLine />}
|
||||
rightSection={
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
setQuery('');
|
||||
searchInputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<RiCloseFill />
|
||||
</ActionIcon>
|
||||
}
|
||||
size="lg"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.currentTarget.value)}
|
||||
/>
|
||||
<Command.Separator />
|
||||
<Command.List>
|
||||
<Command.Empty>No results found.</Command.Empty>
|
||||
{showAlbumGroup && (
|
||||
<Command.Group heading="Albums">
|
||||
{data?.albums?.map((album) => (
|
||||
<Command.Item
|
||||
key={`search-album-${album.id}`}
|
||||
value={`search-${album.id}`}
|
||||
onSelect={() => {
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: album.id }));
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
}}
|
||||
>
|
||||
<LibraryCommandItem
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
id={album.id}
|
||||
imageUrl={album.imageUrl}
|
||||
itemType={LibraryItem.ALBUM}
|
||||
subtitle={album.albumArtists.map((artist) => artist.name).join(', ')}
|
||||
title={album.name}
|
||||
/>
|
||||
</Command.Item>
|
||||
))}
|
||||
</Command.Group>
|
||||
)}
|
||||
{showArtistGroup && (
|
||||
<Command.Group heading="Artists">
|
||||
{data?.albumArtists.map((artist) => (
|
||||
<Command.Item
|
||||
key={`artist-${artist.id}`}
|
||||
value={`search-${artist.id}`}
|
||||
onSelect={() => {
|
||||
navigate(
|
||||
generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||
albumArtistId: artist.id,
|
||||
}),
|
||||
);
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
}}
|
||||
>
|
||||
<LibraryCommandItem
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
id={artist.id}
|
||||
imageUrl={artist.imageUrl}
|
||||
itemType={LibraryItem.ALBUM_ARTIST}
|
||||
subtitle={
|
||||
(artist?.albumCount || 0) > 0 ? `${artist.albumCount} albums` : undefined
|
||||
return (
|
||||
<CustomModal
|
||||
{...modalProps}
|
||||
centered
|
||||
handlers={{
|
||||
...modalProps.handlers,
|
||||
close: () => {
|
||||
if (isHome) {
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
} else {
|
||||
popPage();
|
||||
}
|
||||
title={artist.name}
|
||||
/>
|
||||
</Command.Item>
|
||||
))}
|
||||
</Command.Group>
|
||||
)}
|
||||
{showTrackGroup && (
|
||||
<Command.Group heading="Tracks">
|
||||
{data?.songs.map((song) => (
|
||||
<Command.Item
|
||||
key={`artist-${song.id}`}
|
||||
value={`search-${song.id}`}
|
||||
onSelect={() => {
|
||||
navigate(
|
||||
generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
|
||||
albumId: song.albumId,
|
||||
}),
|
||||
);
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
}}
|
||||
>
|
||||
<LibraryCommandItem
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
id={song.id}
|
||||
imageUrl={song.imageUrl}
|
||||
itemType={LibraryItem.SONG}
|
||||
subtitle={song.artists.map((artist) => artist.name).join(', ')}
|
||||
title={song.name}
|
||||
/>
|
||||
</Command.Item>
|
||||
))}
|
||||
</Command.Group>
|
||||
)}
|
||||
{activePage === CommandPalettePages.HOME && (
|
||||
<HomeCommands
|
||||
handleClose={modalProps.handlers.close}
|
||||
pages={pages}
|
||||
query={query}
|
||||
setPages={setPages}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
)}
|
||||
{activePage === CommandPalettePages.GO_TO && (
|
||||
<GoToCommands
|
||||
handleClose={modalProps.handlers.close}
|
||||
setPages={setPages}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
)}
|
||||
{activePage === CommandPalettePages.MANAGE_SERVERS && (
|
||||
<ServerCommands
|
||||
handleClose={modalProps.handlers.close}
|
||||
setPages={setPages}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
)}
|
||||
</Command.List>
|
||||
</Command>
|
||||
<Paper
|
||||
mt="0.5rem"
|
||||
p="0.5rem"
|
||||
>
|
||||
<Group position="apart">
|
||||
<Command.Loading>{isHome && isLoading && query !== '' && <Spinner />}</Command.Loading>
|
||||
<Group spacing="sm">
|
||||
<Kbd size="md">ESC</Kbd>
|
||||
<Kbd size="md">↑</Kbd>
|
||||
<Kbd size="md">↓</Kbd>
|
||||
<Kbd size="md">⏎</Kbd>
|
||||
</Group>
|
||||
</Group>
|
||||
</Paper>
|
||||
</CustomModal>
|
||||
);
|
||||
},
|
||||
toggle: () => {
|
||||
if (isHome) {
|
||||
modalProps.handlers.toggle();
|
||||
setQuery('');
|
||||
} else {
|
||||
popPage();
|
||||
}
|
||||
},
|
||||
}}
|
||||
scrollAreaComponent={ScrollArea.Autosize}
|
||||
size="lg"
|
||||
>
|
||||
<Group
|
||||
mb="1rem"
|
||||
spacing="sm"
|
||||
>
|
||||
{pages.map((page, index) => (
|
||||
<Fragment key={page}>
|
||||
{index > 0 && ' > '}
|
||||
<Button
|
||||
compact
|
||||
disabled
|
||||
variant="default"
|
||||
>
|
||||
{page?.toLocaleUpperCase()}
|
||||
</Button>
|
||||
</Fragment>
|
||||
))}
|
||||
</Group>
|
||||
<Command
|
||||
filter={(value, search) => {
|
||||
if (value.includes(search)) return 1;
|
||||
if (value.includes('search')) return 1;
|
||||
return 0;
|
||||
}}
|
||||
label="Global Command Menu"
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
>
|
||||
<TextInput
|
||||
ref={searchInputRef}
|
||||
data-autofocus
|
||||
icon={<RiSearchLine />}
|
||||
rightSection={
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
setQuery('');
|
||||
searchInputRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<RiCloseFill />
|
||||
</ActionIcon>
|
||||
}
|
||||
size="lg"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.currentTarget.value)}
|
||||
/>
|
||||
<Command.Separator />
|
||||
<Command.List>
|
||||
<Command.Empty>No results found.</Command.Empty>
|
||||
{showAlbumGroup && (
|
||||
<Command.Group heading="Albums">
|
||||
{data?.albums?.map((album) => (
|
||||
<Command.Item
|
||||
key={`search-album-${album.id}`}
|
||||
value={`search-${album.id}`}
|
||||
onSelect={() => {
|
||||
navigate(
|
||||
generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
|
||||
albumId: album.id,
|
||||
}),
|
||||
);
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
}}
|
||||
>
|
||||
<LibraryCommandItem
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
id={album.id}
|
||||
imageUrl={album.imageUrl}
|
||||
itemType={LibraryItem.ALBUM}
|
||||
subtitle={album.albumArtists
|
||||
.map((artist) => artist.name)
|
||||
.join(', ')}
|
||||
title={album.name}
|
||||
/>
|
||||
</Command.Item>
|
||||
))}
|
||||
</Command.Group>
|
||||
)}
|
||||
{showArtistGroup && (
|
||||
<Command.Group heading="Artists">
|
||||
{data?.albumArtists.map((artist) => (
|
||||
<Command.Item
|
||||
key={`artist-${artist.id}`}
|
||||
value={`search-${artist.id}`}
|
||||
onSelect={() => {
|
||||
navigate(
|
||||
generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||
albumArtistId: artist.id,
|
||||
}),
|
||||
);
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
}}
|
||||
>
|
||||
<LibraryCommandItem
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
id={artist.id}
|
||||
imageUrl={artist.imageUrl}
|
||||
itemType={LibraryItem.ALBUM_ARTIST}
|
||||
subtitle={
|
||||
(artist?.albumCount || 0) > 0
|
||||
? `${artist.albumCount} albums`
|
||||
: undefined
|
||||
}
|
||||
title={artist.name}
|
||||
/>
|
||||
</Command.Item>
|
||||
))}
|
||||
</Command.Group>
|
||||
)}
|
||||
{showTrackGroup && (
|
||||
<Command.Group heading="Tracks">
|
||||
{data?.songs.map((song) => (
|
||||
<Command.Item
|
||||
key={`artist-${song.id}`}
|
||||
value={`search-${song.id}`}
|
||||
onSelect={() => {
|
||||
navigate(
|
||||
generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
|
||||
albumId: song.albumId,
|
||||
}),
|
||||
);
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
}}
|
||||
>
|
||||
<LibraryCommandItem
|
||||
handlePlayQueueAdd={handlePlayQueueAdd}
|
||||
id={song.id}
|
||||
imageUrl={song.imageUrl}
|
||||
itemType={LibraryItem.SONG}
|
||||
subtitle={song.artists
|
||||
.map((artist) => artist.name)
|
||||
.join(', ')}
|
||||
title={song.name}
|
||||
/>
|
||||
</Command.Item>
|
||||
))}
|
||||
</Command.Group>
|
||||
)}
|
||||
{activePage === CommandPalettePages.HOME && (
|
||||
<HomeCommands
|
||||
handleClose={modalProps.handlers.close}
|
||||
pages={pages}
|
||||
query={query}
|
||||
setPages={setPages}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
)}
|
||||
{activePage === CommandPalettePages.GO_TO && (
|
||||
<GoToCommands
|
||||
handleClose={modalProps.handlers.close}
|
||||
setPages={setPages}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
)}
|
||||
{activePage === CommandPalettePages.MANAGE_SERVERS && (
|
||||
<ServerCommands
|
||||
handleClose={modalProps.handlers.close}
|
||||
setPages={setPages}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
)}
|
||||
</Command.List>
|
||||
</Command>
|
||||
<Paper
|
||||
mt="0.5rem"
|
||||
p="0.5rem"
|
||||
>
|
||||
<Group position="apart">
|
||||
<Command.Loading>
|
||||
{isHome && isLoading && query !== '' && <Spinner />}
|
||||
</Command.Loading>
|
||||
<Group spacing="sm">
|
||||
<Kbd size="md">ESC</Kbd>
|
||||
<Kbd size="md">↑</Kbd>
|
||||
<Kbd size="md">↓</Kbd>
|
||||
<Kbd size="md">⏎</Kbd>
|
||||
</Group>
|
||||
</Group>
|
||||
</Paper>
|
||||
</CustomModal>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,69 +2,69 @@ import { Command as Cmdk } from 'cmdk';
|
|||
import styled from 'styled-components';
|
||||
|
||||
export enum CommandPalettePages {
|
||||
GO_TO = 'go',
|
||||
HOME = 'home',
|
||||
MANAGE_SERVERS = 'servers',
|
||||
GO_TO = 'go',
|
||||
HOME = 'home',
|
||||
MANAGE_SERVERS = 'servers',
|
||||
}
|
||||
|
||||
export const Command = styled(Cmdk)`
|
||||
[cmdk-root] {
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
input[cmdk-input] {
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 1.3rem 0.5rem;
|
||||
color: var(--input-fg);
|
||||
font-family: var(--content-font-family);
|
||||
background: var(--input-bg);
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--input-placeholder-fg);
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-group-heading] {
|
||||
margin: 1rem 0;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
[cmdk-group-items] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
[cmdk-item] {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
color: var(--btn-default-fg);
|
||||
font-family: var(--content-font-family);
|
||||
background: var(--btn-default-bg);
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
[cmdk-root] {
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--btn-default-fg-hover);
|
||||
background: var(--btn-default-bg-hover);
|
||||
}
|
||||
}
|
||||
input[cmdk-input] {
|
||||
width: 100%;
|
||||
height: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 1.3rem 0.5rem;
|
||||
color: var(--input-fg);
|
||||
font-family: var(--content-font-family);
|
||||
background: var(--input-bg);
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
|
||||
[cmdk-separator] {
|
||||
height: 1px;
|
||||
margin: 0 0 0.5rem;
|
||||
background: var(--generic-border-color);
|
||||
}
|
||||
&::placeholder {
|
||||
color: var(--input-placeholder-fg);
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-group-heading] {
|
||||
margin: 1rem 0;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
[cmdk-group-items] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
[cmdk-item] {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
color: var(--btn-default-fg);
|
||||
font-family: var(--content-font-family);
|
||||
background: var(--btn-default-bg);
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--btn-default-fg-hover);
|
||||
background: var(--btn-default-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-separator] {
|
||||
height: 1px;
|
||||
margin: 0 0 0.5rem;
|
||||
background: var(--generic-border-color);
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -4,42 +4,42 @@ import { Command, CommandPalettePages } from '/@/renderer/features/search/compon
|
|||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
|
||||
interface GoToCommandsProps {
|
||||
handleClose: () => void;
|
||||
setPages: (pages: CommandPalettePages[]) => void;
|
||||
setQuery: Dispatch<string>;
|
||||
handleClose: () => void;
|
||||
setPages: (pages: CommandPalettePages[]) => void;
|
||||
setQuery: Dispatch<string>;
|
||||
}
|
||||
|
||||
export const GoToCommands = ({ setQuery, setPages, handleClose }: GoToCommandsProps) => {
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goTo = useCallback(
|
||||
(route: string) => {
|
||||
navigate(route);
|
||||
handleClose();
|
||||
setPages([CommandPalettePages.HOME]);
|
||||
setQuery('');
|
||||
},
|
||||
[handleClose, navigate, setPages, setQuery],
|
||||
);
|
||||
const goTo = useCallback(
|
||||
(route: string) => {
|
||||
navigate(route);
|
||||
handleClose();
|
||||
setPages([CommandPalettePages.HOME]);
|
||||
setQuery('');
|
||||
},
|
||||
[handleClose, navigate, setPages, setQuery],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command.Group>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.HOME)}>Home</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.SEARCH)}>Search</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.SETTINGS)}>Settings</Command.Item>
|
||||
</Command.Group>
|
||||
<Command.Group heading="Library">
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_ALBUMS)}>Albums</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_SONGS)}>Tracks</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_ALBUM_ARTISTS)}>
|
||||
Album artists
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_GENRES)}>Genres</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_FOLDERS)}>Folders</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.PLAYLISTS)}>Playlists</Command.Item>
|
||||
</Command.Group>
|
||||
<Command.Separator />
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Command.Group>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.HOME)}>Home</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.SEARCH)}>Search</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.SETTINGS)}>Settings</Command.Item>
|
||||
</Command.Group>
|
||||
<Command.Group heading="Library">
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_ALBUMS)}>Albums</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_SONGS)}>Tracks</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_ALBUM_ARTISTS)}>
|
||||
Album artists
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_GENRES)}>Genres</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_FOLDERS)}>Folders</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.PLAYLISTS)}>Playlists</Command.Item>
|
||||
</Command.Group>
|
||||
<Command.Separator />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,68 +11,70 @@ import { useCurrentServer } from '/@/renderer/store';
|
|||
import { ServerType } from '/@/renderer/types';
|
||||
|
||||
interface HomeCommandsProps {
|
||||
handleClose: () => void;
|
||||
pages: CommandPalettePages[];
|
||||
query: string;
|
||||
setPages: Dispatch<CommandPalettePages[]>;
|
||||
setQuery: Dispatch<string>;
|
||||
handleClose: () => void;
|
||||
pages: CommandPalettePages[];
|
||||
query: string;
|
||||
setPages: Dispatch<CommandPalettePages[]>;
|
||||
setQuery: Dispatch<string>;
|
||||
}
|
||||
|
||||
export const HomeCommands = ({
|
||||
query,
|
||||
setQuery,
|
||||
pages,
|
||||
setPages,
|
||||
handleClose,
|
||||
query,
|
||||
setQuery,
|
||||
pages,
|
||||
setPages,
|
||||
handleClose,
|
||||
}: HomeCommandsProps) => {
|
||||
const navigate = useNavigate();
|
||||
const server = useCurrentServer();
|
||||
const navigate = useNavigate();
|
||||
const server = useCurrentServer();
|
||||
|
||||
const handleCreatePlaylistModal = useCallback(() => {
|
||||
handleClose();
|
||||
const handleCreatePlaylistModal = useCallback(() => {
|
||||
handleClose();
|
||||
|
||||
openModal({
|
||||
children: <CreatePlaylistForm onCancel={() => closeAllModals()} />,
|
||||
size: server?.type === ServerType?.NAVIDROME ? 'xl' : 'sm',
|
||||
title: 'Create Playlist',
|
||||
});
|
||||
}, [handleClose, server?.type]);
|
||||
openModal({
|
||||
children: <CreatePlaylistForm onCancel={() => closeAllModals()} />,
|
||||
size: server?.type === ServerType?.NAVIDROME ? 'xl' : 'sm',
|
||||
title: 'Create Playlist',
|
||||
});
|
||||
}, [handleClose, server?.type]);
|
||||
|
||||
const handleSearch = () => {
|
||||
navigate(
|
||||
{
|
||||
pathname: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.SONG }),
|
||||
search: createSearchParams({
|
||||
query,
|
||||
}).toString(),
|
||||
},
|
||||
{
|
||||
state: {
|
||||
navigationId: nanoid(),
|
||||
},
|
||||
},
|
||||
const handleSearch = () => {
|
||||
navigate(
|
||||
{
|
||||
pathname: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.SONG }),
|
||||
search: createSearchParams({
|
||||
query,
|
||||
}).toString(),
|
||||
},
|
||||
{
|
||||
state: {
|
||||
navigationId: nanoid(),
|
||||
},
|
||||
},
|
||||
);
|
||||
handleClose();
|
||||
setQuery('');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command.Group heading="Commands">
|
||||
<Command.Item
|
||||
value="Search"
|
||||
onSelect={handleSearch}
|
||||
>
|
||||
{query ? `Search for "${query}"...` : 'Search...'}
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={handleCreatePlaylistModal}>Create playlist...</Command.Item>
|
||||
<Command.Item onSelect={() => setPages([...pages, CommandPalettePages.GO_TO])}>
|
||||
Go to page...
|
||||
</Command.Item>
|
||||
<Command.Item
|
||||
onSelect={() => setPages([...pages, CommandPalettePages.MANAGE_SERVERS])}
|
||||
>
|
||||
Server commands...
|
||||
</Command.Item>
|
||||
</Command.Group>
|
||||
</>
|
||||
);
|
||||
handleClose();
|
||||
setQuery('');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command.Group heading="Commands">
|
||||
<Command.Item
|
||||
value="Search"
|
||||
onSelect={handleSearch}
|
||||
>
|
||||
{query ? `Search for "${query}"...` : 'Search...'}
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={handleCreatePlaylistModal}>Create playlist...</Command.Item>
|
||||
<Command.Item onSelect={() => setPages([...pages, CommandPalettePages.GO_TO])}>
|
||||
Go to page...
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={() => setPages([...pages, CommandPalettePages.MANAGE_SERVERS])}>
|
||||
Server commands...
|
||||
</Command.Item>
|
||||
</Command.Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Center, Flex } from '@mantine/core';
|
||||
import { useCallback, MouseEvent } from 'react';
|
||||
import {
|
||||
RiAddBoxFill,
|
||||
RiAddCircleFill,
|
||||
RiAlbumFill,
|
||||
RiPlayFill,
|
||||
RiPlayListFill,
|
||||
RiUserVoiceFill,
|
||||
RiAddBoxFill,
|
||||
RiAddCircleFill,
|
||||
RiAlbumFill,
|
||||
RiPlayFill,
|
||||
RiPlayListFill,
|
||||
RiUserVoiceFill,
|
||||
} from 'react-icons/ri';
|
||||
import styled from 'styled-components';
|
||||
import { LibraryItem } from '/@/renderer/api/types';
|
||||
|
|
@ -16,168 +16,168 @@ import { Play, PlayQueueAddOptions } from '/@/renderer/types';
|
|||
const Item = styled(Flex)``;
|
||||
|
||||
const ItemGrid = styled.div<{ height: number }>`
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-template-areas: 'image info';
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: ${(props) => props.height}px minmax(0, 1fr);
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
letter-spacing: 0.5px;
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-template-areas: 'image info';
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-columns: ${(props) => props.height}px minmax(0, 1fr);
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
letter-spacing: 0.5px;
|
||||
`;
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
display: flex;
|
||||
grid-area: image;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
grid-area: image;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const MetadataWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-area: info;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-area: info;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledImage = styled.img`
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
const ActionsContainer = styled(Flex)``;
|
||||
|
||||
interface LibraryCommandItemProps {
|
||||
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
|
||||
id: string;
|
||||
imageUrl: string | null;
|
||||
itemType: LibraryItem;
|
||||
subtitle?: string;
|
||||
title?: string;
|
||||
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
|
||||
id: string;
|
||||
imageUrl: string | null;
|
||||
itemType: LibraryItem;
|
||||
subtitle?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const LibraryCommandItem = ({
|
||||
id,
|
||||
imageUrl,
|
||||
subtitle,
|
||||
title,
|
||||
itemType,
|
||||
handlePlayQueueAdd,
|
||||
id,
|
||||
imageUrl,
|
||||
subtitle,
|
||||
title,
|
||||
itemType,
|
||||
handlePlayQueueAdd,
|
||||
}: LibraryCommandItemProps) => {
|
||||
let Placeholder = RiAlbumFill;
|
||||
let Placeholder = RiAlbumFill;
|
||||
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
Placeholder = RiAlbumFill;
|
||||
break;
|
||||
case LibraryItem.ARTIST:
|
||||
Placeholder = RiUserVoiceFill;
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
Placeholder = RiUserVoiceFill;
|
||||
break;
|
||||
case LibraryItem.PLAYLIST:
|
||||
Placeholder = RiPlayListFill;
|
||||
break;
|
||||
default:
|
||||
Placeholder = RiAlbumFill;
|
||||
break;
|
||||
}
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
Placeholder = RiAlbumFill;
|
||||
break;
|
||||
case LibraryItem.ARTIST:
|
||||
Placeholder = RiUserVoiceFill;
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
Placeholder = RiUserVoiceFill;
|
||||
break;
|
||||
case LibraryItem.PLAYLIST:
|
||||
Placeholder = RiPlayListFill;
|
||||
break;
|
||||
default:
|
||||
Placeholder = RiAlbumFill;
|
||||
break;
|
||||
}
|
||||
|
||||
const handlePlay = useCallback(
|
||||
(e: MouseEvent, id: string, playType: Play) => {
|
||||
e.stopPropagation();
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: [id],
|
||||
type: itemType,
|
||||
const handlePlay = useCallback(
|
||||
(e: MouseEvent, id: string, playType: Play) => {
|
||||
e.stopPropagation();
|
||||
handlePlayQueueAdd?.({
|
||||
byItemType: {
|
||||
id: [id],
|
||||
type: itemType,
|
||||
},
|
||||
playType,
|
||||
});
|
||||
},
|
||||
playType,
|
||||
});
|
||||
},
|
||||
[handlePlayQueueAdd, itemType],
|
||||
);
|
||||
[handlePlayQueueAdd, itemType],
|
||||
);
|
||||
|
||||
return (
|
||||
<Item
|
||||
gap="xl"
|
||||
justify="space-between"
|
||||
style={{ height: '40px', width: '100%' }}
|
||||
>
|
||||
<ItemGrid height={40}>
|
||||
<ImageWrapper>
|
||||
{imageUrl ? (
|
||||
<StyledImage
|
||||
alt="cover"
|
||||
height={40}
|
||||
placeholder="var(--placeholder-bg)"
|
||||
src={imageUrl}
|
||||
style={{}}
|
||||
width={40}
|
||||
/>
|
||||
) : (
|
||||
<Center
|
||||
style={{
|
||||
background: 'var(--placeholder-bg)',
|
||||
borderRadius: 'var(--card-default-radius)',
|
||||
height: `${40}px`,
|
||||
width: `${40}px`,
|
||||
}}
|
||||
return (
|
||||
<Item
|
||||
gap="xl"
|
||||
justify="space-between"
|
||||
style={{ height: '40px', width: '100%' }}
|
||||
>
|
||||
<ItemGrid height={40}>
|
||||
<ImageWrapper>
|
||||
{imageUrl ? (
|
||||
<StyledImage
|
||||
alt="cover"
|
||||
height={40}
|
||||
placeholder="var(--placeholder-bg)"
|
||||
src={imageUrl}
|
||||
style={{}}
|
||||
width={40}
|
||||
/>
|
||||
) : (
|
||||
<Center
|
||||
style={{
|
||||
background: 'var(--placeholder-bg)',
|
||||
borderRadius: 'var(--card-default-radius)',
|
||||
height: `${40}px`,
|
||||
width: `${40}px`,
|
||||
}}
|
||||
>
|
||||
<Placeholder
|
||||
color="var(--placeholder-fg)"
|
||||
size={35}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
</ImageWrapper>
|
||||
<MetadataWrapper>
|
||||
<Text overflow="hidden">{title}</Text>
|
||||
<Text
|
||||
$secondary
|
||||
overflow="hidden"
|
||||
>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</MetadataWrapper>
|
||||
</ItemGrid>
|
||||
<ActionsContainer
|
||||
align="center"
|
||||
gap="sm"
|
||||
justify="flex-end"
|
||||
>
|
||||
<Placeholder
|
||||
color="var(--placeholder-fg)"
|
||||
size={35}
|
||||
/>
|
||||
</Center>
|
||||
)}
|
||||
</ImageWrapper>
|
||||
<MetadataWrapper>
|
||||
<Text overflow="hidden">{title}</Text>
|
||||
<Text
|
||||
$secondary
|
||||
overflow="hidden"
|
||||
>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</MetadataWrapper>
|
||||
</ItemGrid>
|
||||
<ActionsContainer
|
||||
align="center"
|
||||
gap="sm"
|
||||
justify="flex-end"
|
||||
>
|
||||
<Button
|
||||
compact
|
||||
size="md"
|
||||
tooltip={{ label: 'Play', openDelay: 500 }}
|
||||
variant="default"
|
||||
onClick={(e) => handlePlay(e, id, Play.NOW)}
|
||||
>
|
||||
<RiPlayFill />
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
size="md"
|
||||
tooltip={{ label: 'Add to queue', openDelay: 500 }}
|
||||
variant="default"
|
||||
onClick={(e) => handlePlay(e, id, Play.LAST)}
|
||||
>
|
||||
<RiAddBoxFill />
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
size="md"
|
||||
tooltip={{ label: 'Play next', openDelay: 500 }}
|
||||
variant="default"
|
||||
onClick={(e) => handlePlay(e, id, Play.NEXT)}
|
||||
>
|
||||
<RiAddCircleFill />
|
||||
</Button>
|
||||
</ActionsContainer>
|
||||
</Item>
|
||||
);
|
||||
<Button
|
||||
compact
|
||||
size="md"
|
||||
tooltip={{ label: 'Play', openDelay: 500 }}
|
||||
variant="default"
|
||||
onClick={(e) => handlePlay(e, id, Play.NOW)}
|
||||
>
|
||||
<RiPlayFill />
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
size="md"
|
||||
tooltip={{ label: 'Add to queue', openDelay: 500 }}
|
||||
variant="default"
|
||||
onClick={(e) => handlePlay(e, id, Play.LAST)}
|
||||
>
|
||||
<RiAddBoxFill />
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
size="md"
|
||||
tooltip={{ label: 'Play next', openDelay: 500 }}
|
||||
variant="default"
|
||||
onClick={(e) => handlePlay(e, id, Play.NEXT)}
|
||||
>
|
||||
<RiAddCircleFill />
|
||||
</Button>
|
||||
</ActionsContainer>
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { MutableRefObject, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
ColDef,
|
||||
GridReadyEvent,
|
||||
RowDoubleClickedEvent,
|
||||
IDatasource,
|
||||
ColDef,
|
||||
GridReadyEvent,
|
||||
RowDoubleClickedEvent,
|
||||
IDatasource,
|
||||
} from '@ag-grid-community/core';
|
||||
import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact';
|
||||
import { Stack } from '@mantine/core';
|
||||
|
|
@ -14,139 +14,143 @@ import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-gr
|
|||
import { VirtualTable, getColumnDefs } from '/@/renderer/components/virtual-table';
|
||||
import { useHandleTableContextMenu } from '/@/renderer/features/context-menu';
|
||||
import {
|
||||
ALBUM_CONTEXT_MENU_ITEMS,
|
||||
ARTIST_CONTEXT_MENU_ITEMS,
|
||||
SONG_CONTEXT_MENU_ITEMS,
|
||||
ALBUM_CONTEXT_MENU_ITEMS,
|
||||
ARTIST_CONTEXT_MENU_ITEMS,
|
||||
SONG_CONTEXT_MENU_ITEMS,
|
||||
} from '/@/renderer/features/context-menu/context-menu-items';
|
||||
import { usePlayQueueAdd } from '/@/renderer/features/player';
|
||||
import { AppRoute } from '../../../router/routes';
|
||||
import {
|
||||
useCurrentServer,
|
||||
useSongListStore,
|
||||
usePlayButtonBehavior,
|
||||
useAlbumListStore,
|
||||
useAlbumArtistListStore,
|
||||
useCurrentServer,
|
||||
useSongListStore,
|
||||
usePlayButtonBehavior,
|
||||
useAlbumListStore,
|
||||
useAlbumArtistListStore,
|
||||
} from '/@/renderer/store';
|
||||
|
||||
interface SearchContentProps {
|
||||
getDatasource: (searchQuery: string, itemType: LibraryItem) => IDatasource | undefined;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
getDatasource: (searchQuery: string, itemType: LibraryItem) => IDatasource | undefined;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const SearchContent = ({ tableRef, getDatasource }: SearchContentProps) => {
|
||||
const navigate = useNavigate();
|
||||
const server = useCurrentServer();
|
||||
const { itemType } = useParams() as { itemType: LibraryItem };
|
||||
const [searchParams] = useSearchParams();
|
||||
const songListStore = useSongListStore();
|
||||
const albumListStore = useAlbumListStore();
|
||||
const albumArtistListStore = useAlbumArtistListStore();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
const navigate = useNavigate();
|
||||
const server = useCurrentServer();
|
||||
const { itemType } = useParams() as { itemType: LibraryItem };
|
||||
const [searchParams] = useSearchParams();
|
||||
const songListStore = useSongListStore();
|
||||
const albumListStore = useAlbumListStore();
|
||||
const albumArtistListStore = useAlbumArtistListStore();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const playButtonBehavior = usePlayButtonBehavior();
|
||||
|
||||
const getTable = useCallback(
|
||||
(itemType: string) => {
|
||||
switch (itemType) {
|
||||
case LibraryItem.SONG:
|
||||
return songListStore.table;
|
||||
case LibraryItem.ALBUM:
|
||||
return albumListStore.table;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
return albumArtistListStore.table;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
[albumArtistListStore.table, albumListStore.table, songListStore.table],
|
||||
);
|
||||
const getTable = useCallback(
|
||||
(itemType: string) => {
|
||||
switch (itemType) {
|
||||
case LibraryItem.SONG:
|
||||
return songListStore.table;
|
||||
case LibraryItem.ALBUM:
|
||||
return albumListStore.table;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
return albumArtistListStore.table;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
[albumArtistListStore.table, albumListStore.table, songListStore.table],
|
||||
);
|
||||
|
||||
const table = getTable(itemType)!;
|
||||
const table = getTable(itemType)!;
|
||||
|
||||
const columnDefs: ColDef[] = useMemo(() => getColumnDefs(table.columns), [table.columns]);
|
||||
const columnDefs: ColDef[] = useMemo(() => getColumnDefs(table.columns), [table.columns]);
|
||||
|
||||
const onGridReady = useCallback(
|
||||
(params: GridReadyEvent) => {
|
||||
const datasource = getDatasource(searchParams.get('query') || '', itemType);
|
||||
if (!datasource) return;
|
||||
const onGridReady = useCallback(
|
||||
(params: GridReadyEvent) => {
|
||||
const datasource = getDatasource(searchParams.get('query') || '', itemType);
|
||||
if (!datasource) return;
|
||||
|
||||
params.api.setDatasource(datasource);
|
||||
params.api.ensureIndexVisible(table.scrollOffset, 'top');
|
||||
},
|
||||
[getDatasource, itemType, searchParams, table.scrollOffset],
|
||||
);
|
||||
params.api.setDatasource(datasource);
|
||||
params.api.ensureIndexVisible(table.scrollOffset, 'top');
|
||||
},
|
||||
[getDatasource, itemType, searchParams, table.scrollOffset],
|
||||
);
|
||||
|
||||
const handleGridSizeChange = () => {
|
||||
if (table.autoFit) {
|
||||
tableRef?.current?.api.sizeColumnsToFit();
|
||||
}
|
||||
};
|
||||
const handleGridSizeChange = () => {
|
||||
if (table.autoFit) {
|
||||
tableRef?.current?.api.sizeColumnsToFit();
|
||||
}
|
||||
};
|
||||
|
||||
const contextMenuItems = () => {
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
return ALBUM_CONTEXT_MENU_ITEMS;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
return ARTIST_CONTEXT_MENU_ITEMS;
|
||||
case LibraryItem.SONG:
|
||||
return SONG_CONTEXT_MENU_ITEMS;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
const contextMenuItems = () => {
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
return ALBUM_CONTEXT_MENU_ITEMS;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
return ARTIST_CONTEXT_MENU_ITEMS;
|
||||
case LibraryItem.SONG:
|
||||
return SONG_CONTEXT_MENU_ITEMS;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const handleContextMenu = useHandleTableContextMenu(itemType, contextMenuItems());
|
||||
const handleContextMenu = useHandleTableContextMenu(itemType, contextMenuItems());
|
||||
|
||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
|
||||
if (!e.data) return;
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { albumArtistId: e.data.id }));
|
||||
break;
|
||||
case LibraryItem.SONG:
|
||||
handlePlayQueueAdd?.({
|
||||
byData: [e.data],
|
||||
playType: playButtonBehavior,
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
|
||||
if (!e.data) return;
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: e.data.id }));
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
navigate(
|
||||
generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||
albumArtistId: e.data.id,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case LibraryItem.SONG:
|
||||
handlePlayQueueAdd?.({
|
||||
byData: [e.data],
|
||||
playType: playButtonBehavior,
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
h="100%"
|
||||
spacing={0}
|
||||
>
|
||||
<VirtualGridAutoSizerContainer>
|
||||
<VirtualTable
|
||||
// https://github.com/ag-grid/ag-grid/issues/5284
|
||||
// Key is used to force remount of table when display, rowHeight, or server changes
|
||||
key={`table-${itemType}-${table.rowHeight}-${server?.id}`}
|
||||
ref={tableRef}
|
||||
alwaysShowHorizontalScroll
|
||||
suppressRowDrag
|
||||
autoFitColumns={table.autoFit}
|
||||
blockLoadDebounceMillis={200}
|
||||
cacheBlockSize={25}
|
||||
cacheOverflowSize={1}
|
||||
columnDefs={columnDefs}
|
||||
context={{
|
||||
query: searchParams.get('query'),
|
||||
}}
|
||||
getRowId={(data) => data.data.id}
|
||||
infiniteInitialRowCount={25}
|
||||
rowBuffer={20}
|
||||
rowHeight={table.rowHeight || 40}
|
||||
rowModelType="infinite"
|
||||
rowSelection="multiple"
|
||||
onCellContextMenu={handleContextMenu}
|
||||
onGridReady={onGridReady}
|
||||
onGridSizeChanged={handleGridSizeChange}
|
||||
onRowDoubleClicked={handleRowDoubleClick}
|
||||
/>
|
||||
</VirtualGridAutoSizerContainer>
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack
|
||||
h="100%"
|
||||
spacing={0}
|
||||
>
|
||||
<VirtualGridAutoSizerContainer>
|
||||
<VirtualTable
|
||||
// https://github.com/ag-grid/ag-grid/issues/5284
|
||||
// Key is used to force remount of table when display, rowHeight, or server changes
|
||||
key={`table-${itemType}-${table.rowHeight}-${server?.id}`}
|
||||
ref={tableRef}
|
||||
alwaysShowHorizontalScroll
|
||||
suppressRowDrag
|
||||
autoFitColumns={table.autoFit}
|
||||
blockLoadDebounceMillis={200}
|
||||
cacheBlockSize={25}
|
||||
cacheOverflowSize={1}
|
||||
columnDefs={columnDefs}
|
||||
context={{
|
||||
query: searchParams.get('query'),
|
||||
}}
|
||||
getRowId={(data) => data.data.id}
|
||||
infiniteInitialRowCount={25}
|
||||
rowBuffer={20}
|
||||
rowHeight={table.rowHeight || 40}
|
||||
rowModelType="infinite"
|
||||
rowSelection="multiple"
|
||||
onCellContextMenu={handleContextMenu}
|
||||
onGridReady={onGridReady}
|
||||
onGridSizeChanged={handleGridSizeChange}
|
||||
onRowDoubleClicked={handleRowDoubleClick}
|
||||
/>
|
||||
</VirtualGridAutoSizerContainer>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,96 +11,100 @@ import { useContainerQuery } from '/@/renderer/hooks';
|
|||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
|
||||
interface SearchHeaderProps {
|
||||
getDatasource: (searchQuery: string, itemType: LibraryItem) => IDatasource | undefined;
|
||||
navigationId: string;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
getDatasource: (searchQuery: string, itemType: LibraryItem) => IDatasource | undefined;
|
||||
navigationId: string;
|
||||
tableRef: MutableRefObject<AgGridReactType | null>;
|
||||
}
|
||||
|
||||
export const SearchHeader = ({ tableRef, getDatasource, navigationId }: SearchHeaderProps) => {
|
||||
const { itemType } = useParams() as { itemType: LibraryItem };
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const cq = useContainerQuery();
|
||||
const { itemType } = useParams() as { itemType: LibraryItem };
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const cq = useContainerQuery();
|
||||
|
||||
const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.value) return;
|
||||
setSearchParams({ query: e.target.value }, { replace: true, state: { navigationId } });
|
||||
const datasource = getDatasource(e.target.value, itemType);
|
||||
if (!datasource) return;
|
||||
tableRef.current?.api.setDatasource(datasource);
|
||||
}, 200);
|
||||
const handleSearch = debounce((e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.value) return;
|
||||
setSearchParams({ query: e.target.value }, { replace: true, state: { navigationId } });
|
||||
const datasource = getDatasource(e.target.value, itemType);
|
||||
if (!datasource) return;
|
||||
tableRef.current?.api.setDatasource(datasource);
|
||||
}, 200);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
ref={cq.ref}
|
||||
spacing={0}
|
||||
>
|
||||
<PageHeader>
|
||||
<Flex
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
return (
|
||||
<Stack
|
||||
ref={cq.ref}
|
||||
spacing={0}
|
||||
>
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.Title>Search</LibraryHeaderBar.Title>
|
||||
</LibraryHeaderBar>
|
||||
<Group>
|
||||
<SearchInput
|
||||
// key={`search-input-${initialQuery}`}
|
||||
defaultValue={searchParams.get('query') || ''}
|
||||
openedWidth={cq.isMd ? 250 : cq.isSm ? 200 : 150}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</Group>
|
||||
</Flex>
|
||||
</PageHeader>
|
||||
<FilterBar>
|
||||
<Group>
|
||||
<Button
|
||||
compact
|
||||
replace
|
||||
component={Link}
|
||||
fw={600}
|
||||
size="md"
|
||||
state={{ navigationId }}
|
||||
to={{
|
||||
pathname: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.SONG }),
|
||||
search: searchParams.toString(),
|
||||
}}
|
||||
variant={itemType === LibraryItem.SONG ? 'filled' : 'subtle'}
|
||||
>
|
||||
Tracks
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
replace
|
||||
component={Link}
|
||||
fw={600}
|
||||
size="md"
|
||||
state={{ navigationId }}
|
||||
to={{
|
||||
pathname: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.ALBUM }),
|
||||
search: searchParams.toString(),
|
||||
}}
|
||||
variant={itemType === LibraryItem.ALBUM ? 'filled' : 'subtle'}
|
||||
>
|
||||
Albums
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
replace
|
||||
component={Link}
|
||||
fw={600}
|
||||
size="md"
|
||||
state={{ navigationId }}
|
||||
to={{
|
||||
pathname: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.ALBUM_ARTIST }),
|
||||
search: searchParams.toString(),
|
||||
}}
|
||||
variant={itemType === LibraryItem.ALBUM_ARTIST ? 'filled' : 'subtle'}
|
||||
>
|
||||
Artists
|
||||
</Button>
|
||||
</Group>
|
||||
</FilterBar>
|
||||
</Stack>
|
||||
);
|
||||
<PageHeader>
|
||||
<Flex
|
||||
justify="space-between"
|
||||
w="100%"
|
||||
>
|
||||
<LibraryHeaderBar>
|
||||
<LibraryHeaderBar.Title>Search</LibraryHeaderBar.Title>
|
||||
</LibraryHeaderBar>
|
||||
<Group>
|
||||
<SearchInput
|
||||
// key={`search-input-${initialQuery}`}
|
||||
defaultValue={searchParams.get('query') || ''}
|
||||
openedWidth={cq.isMd ? 250 : cq.isSm ? 200 : 150}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</Group>
|
||||
</Flex>
|
||||
</PageHeader>
|
||||
<FilterBar>
|
||||
<Group>
|
||||
<Button
|
||||
compact
|
||||
replace
|
||||
component={Link}
|
||||
fw={600}
|
||||
size="md"
|
||||
state={{ navigationId }}
|
||||
to={{
|
||||
pathname: generatePath(AppRoute.SEARCH, { itemType: LibraryItem.SONG }),
|
||||
search: searchParams.toString(),
|
||||
}}
|
||||
variant={itemType === LibraryItem.SONG ? 'filled' : 'subtle'}
|
||||
>
|
||||
Tracks
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
replace
|
||||
component={Link}
|
||||
fw={600}
|
||||
size="md"
|
||||
state={{ navigationId }}
|
||||
to={{
|
||||
pathname: generatePath(AppRoute.SEARCH, {
|
||||
itemType: LibraryItem.ALBUM,
|
||||
}),
|
||||
search: searchParams.toString(),
|
||||
}}
|
||||
variant={itemType === LibraryItem.ALBUM ? 'filled' : 'subtle'}
|
||||
>
|
||||
Albums
|
||||
</Button>
|
||||
<Button
|
||||
compact
|
||||
replace
|
||||
component={Link}
|
||||
fw={600}
|
||||
size="md"
|
||||
state={{ navigationId }}
|
||||
to={{
|
||||
pathname: generatePath(AppRoute.SEARCH, {
|
||||
itemType: LibraryItem.ALBUM_ARTIST,
|
||||
}),
|
||||
search: searchParams.toString(),
|
||||
}}
|
||||
variant={itemType === LibraryItem.ALBUM_ARTIST ? 'filled' : 'subtle'}
|
||||
>
|
||||
Artists
|
||||
</Button>
|
||||
</Group>
|
||||
</FilterBar>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,51 +8,51 @@ import { useNavigate } from 'react-router';
|
|||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
|
||||
interface ServerCommandsProps {
|
||||
handleClose: () => void;
|
||||
setPages: (pages: CommandPalettePages[]) => void;
|
||||
setQuery: Dispatch<string>;
|
||||
handleClose: () => void;
|
||||
setPages: (pages: CommandPalettePages[]) => void;
|
||||
setQuery: Dispatch<string>;
|
||||
}
|
||||
|
||||
export const ServerCommands = ({ setQuery, setPages, handleClose }: ServerCommandsProps) => {
|
||||
const serverList = useServerList();
|
||||
const navigate = useNavigate();
|
||||
const { setCurrentServer } = useAuthStoreActions();
|
||||
const serverList = useServerList();
|
||||
const navigate = useNavigate();
|
||||
const { setCurrentServer } = useAuthStoreActions();
|
||||
|
||||
const handleManageServersModal = useCallback(() => {
|
||||
openModal({
|
||||
children: <ServerList />,
|
||||
title: 'Manage Servers',
|
||||
});
|
||||
handleClose();
|
||||
setQuery('');
|
||||
setPages([CommandPalettePages.HOME]);
|
||||
}, [handleClose, setPages, setQuery]);
|
||||
const handleManageServersModal = useCallback(() => {
|
||||
openModal({
|
||||
children: <ServerList />,
|
||||
title: 'Manage Servers',
|
||||
});
|
||||
handleClose();
|
||||
setQuery('');
|
||||
setPages([CommandPalettePages.HOME]);
|
||||
}, [handleClose, setPages, setQuery]);
|
||||
|
||||
const handleSelectServer = useCallback(
|
||||
(server: ServerListItem) => {
|
||||
navigate(AppRoute.HOME);
|
||||
setCurrentServer(server);
|
||||
handleClose();
|
||||
setQuery('');
|
||||
setPages([CommandPalettePages.HOME]);
|
||||
},
|
||||
[handleClose, navigate, setCurrentServer, setPages, setQuery],
|
||||
);
|
||||
const handleSelectServer = useCallback(
|
||||
(server: ServerListItem) => {
|
||||
navigate(AppRoute.HOME);
|
||||
setCurrentServer(server);
|
||||
handleClose();
|
||||
setQuery('');
|
||||
setPages([CommandPalettePages.HOME]);
|
||||
},
|
||||
[handleClose, navigate, setCurrentServer, setPages, setQuery],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command.Group heading="Select a server">
|
||||
{Object.keys(serverList).map((key) => (
|
||||
<Command.Item
|
||||
key={key}
|
||||
onSelect={() => handleSelectServer(serverList[key])}
|
||||
>{`Switch to ${serverList[key].name}...`}</Command.Item>
|
||||
))}
|
||||
</Command.Group>
|
||||
<Command.Group heading="Manage">
|
||||
<Command.Item onSelect={handleManageServersModal}>Manage servers...</Command.Item>
|
||||
</Command.Group>
|
||||
<Command.Separator />
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Command.Group heading="Select a server">
|
||||
{Object.keys(serverList).map((key) => (
|
||||
<Command.Item
|
||||
key={key}
|
||||
onSelect={() => handleSelectServer(serverList[key])}
|
||||
>{`Switch to ${serverList[key].name}...`}</Command.Item>
|
||||
))}
|
||||
</Command.Group>
|
||||
<Command.Group heading="Manage">
|
||||
<Command.Item onSelect={handleManageServersModal}>Manage servers...</Command.Item>
|
||||
</Command.Group>
|
||||
<Command.Separator />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,22 +6,22 @@ import { getServerById } from '/@/renderer/store';
|
|||
import { api } from '/@/renderer/api';
|
||||
|
||||
export const useSearch = (args: QueryHookArgs<SearchQuery>) => {
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
const { options, query, serverId } = args;
|
||||
const server = getServerById(serverId);
|
||||
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.search({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
return useQuery({
|
||||
enabled: !!serverId,
|
||||
queryFn: ({ signal }) => {
|
||||
if (!server) throw new Error('Server not found');
|
||||
return api.controller.search({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
});
|
||||
},
|
||||
query,
|
||||
});
|
||||
},
|
||||
queryKey: queryKeys.search.list(serverId || '', query),
|
||||
...options,
|
||||
});
|
||||
queryKey: queryKeys.search.list(serverId || '', query),
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,175 +12,175 @@ import { LibraryItem, SearchQuery } from '/@/renderer/api/types';
|
|||
import { useLocation, useParams } from 'react-router';
|
||||
|
||||
const SearchRoute = () => {
|
||||
const { state: locationState } = useLocation();
|
||||
const localNavigationId = useId();
|
||||
const navigationId = locationState?.navigationId || localNavigationId;
|
||||
const { itemType } = useParams() as { itemType: string };
|
||||
const tableRef = useRef<AgGridReactType | null>(null);
|
||||
const server = useCurrentServer();
|
||||
const queryClient = useQueryClient();
|
||||
const { state: locationState } = useLocation();
|
||||
const localNavigationId = useId();
|
||||
const navigationId = locationState?.navigationId || localNavigationId;
|
||||
const { itemType } = useParams() as { itemType: string };
|
||||
const tableRef = useRef<AgGridReactType | null>(null);
|
||||
const server = useCurrentServer();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const getDatasource = useCallback(
|
||||
(searchQuery: string, itemType: LibraryItem) => {
|
||||
let dataSource: IDatasource | undefined;
|
||||
const getDatasource = useCallback(
|
||||
(searchQuery: string, itemType: LibraryItem) => {
|
||||
let dataSource: IDatasource | undefined;
|
||||
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
dataSource = {
|
||||
getRows: async (params) => {
|
||||
const limit = params.endRow - params.startRow;
|
||||
const startIndex = params.startRow;
|
||||
switch (itemType) {
|
||||
case LibraryItem.ALBUM:
|
||||
dataSource = {
|
||||
getRows: async (params) => {
|
||||
const limit = params.endRow - params.startRow;
|
||||
const startIndex = params.startRow;
|
||||
|
||||
const query: SearchQuery = {
|
||||
albumArtistLimit: 0,
|
||||
albumArtistStartIndex: 0,
|
||||
albumLimit: limit,
|
||||
albumStartIndex: startIndex,
|
||||
query: searchQuery || ' ',
|
||||
songLimit: 0,
|
||||
songStartIndex: 0,
|
||||
};
|
||||
const query: SearchQuery = {
|
||||
albumArtistLimit: 0,
|
||||
albumArtistStartIndex: 0,
|
||||
albumLimit: limit,
|
||||
albumStartIndex: startIndex,
|
||||
query: searchQuery || ' ',
|
||||
songLimit: 0,
|
||||
songStartIndex: 0,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.search.list(server?.id || '', query);
|
||||
const queryKey = queryKeys.search.list(server?.id || '', query);
|
||||
|
||||
const res = await queryClient.fetchQuery(
|
||||
queryKey,
|
||||
async ({ signal }) =>
|
||||
api.controller.search({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
}),
|
||||
{ cacheTime: 1000 * 60 },
|
||||
);
|
||||
const res = await queryClient.fetchQuery(
|
||||
queryKey,
|
||||
async ({ signal }) =>
|
||||
api.controller.search({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
}),
|
||||
{ cacheTime: 1000 * 60 },
|
||||
);
|
||||
|
||||
if (!res) return;
|
||||
if (!res) return;
|
||||
|
||||
const items = res.albums || [];
|
||||
const numOfItems = items.length;
|
||||
const items = res.albums || [];
|
||||
const numOfItems = items.length;
|
||||
|
||||
let lastRow = -1;
|
||||
if (numOfItems < limit) {
|
||||
lastRow = startIndex + numOfItems;
|
||||
}
|
||||
let lastRow = -1;
|
||||
if (numOfItems < limit) {
|
||||
lastRow = startIndex + numOfItems;
|
||||
}
|
||||
|
||||
params.successCallback(items, lastRow);
|
||||
},
|
||||
};
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
dataSource = {
|
||||
getRows: async (params) => {
|
||||
const limit = params.endRow - params.startRow;
|
||||
const startIndex = params.startRow;
|
||||
params.successCallback(items, lastRow);
|
||||
},
|
||||
};
|
||||
break;
|
||||
case LibraryItem.ALBUM_ARTIST:
|
||||
dataSource = {
|
||||
getRows: async (params) => {
|
||||
const limit = params.endRow - params.startRow;
|
||||
const startIndex = params.startRow;
|
||||
|
||||
const query: SearchQuery = {
|
||||
albumArtistLimit: limit,
|
||||
albumArtistStartIndex: startIndex,
|
||||
albumLimit: 0,
|
||||
albumStartIndex: 0,
|
||||
query: searchQuery || ' ',
|
||||
songLimit: 0,
|
||||
songStartIndex: 0,
|
||||
};
|
||||
const query: SearchQuery = {
|
||||
albumArtistLimit: limit,
|
||||
albumArtistStartIndex: startIndex,
|
||||
albumLimit: 0,
|
||||
albumStartIndex: 0,
|
||||
query: searchQuery || ' ',
|
||||
songLimit: 0,
|
||||
songStartIndex: 0,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.search.list(server?.id || '', query);
|
||||
const queryKey = queryKeys.search.list(server?.id || '', query);
|
||||
|
||||
const res = await queryClient.fetchQuery(
|
||||
queryKey,
|
||||
async ({ signal }) =>
|
||||
api.controller.search({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
}),
|
||||
{ cacheTime: 1000 * 60 },
|
||||
);
|
||||
const res = await queryClient.fetchQuery(
|
||||
queryKey,
|
||||
async ({ signal }) =>
|
||||
api.controller.search({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
}),
|
||||
{ cacheTime: 1000 * 60 },
|
||||
);
|
||||
|
||||
if (!res) return;
|
||||
if (!res) return;
|
||||
|
||||
const items = res.albumArtists || [];
|
||||
const numOfItems = items.length;
|
||||
const items = res.albumArtists || [];
|
||||
const numOfItems = items.length;
|
||||
|
||||
let lastRow = -1;
|
||||
if (numOfItems < limit) {
|
||||
lastRow = startIndex + numOfItems;
|
||||
}
|
||||
let lastRow = -1;
|
||||
if (numOfItems < limit) {
|
||||
lastRow = startIndex + numOfItems;
|
||||
}
|
||||
|
||||
params.successCallback(items, lastRow);
|
||||
},
|
||||
};
|
||||
break;
|
||||
case LibraryItem.SONG:
|
||||
dataSource = {
|
||||
getRows: async (params) => {
|
||||
const limit = params.endRow - params.startRow;
|
||||
const startIndex = params.startRow;
|
||||
params.successCallback(items, lastRow);
|
||||
},
|
||||
};
|
||||
break;
|
||||
case LibraryItem.SONG:
|
||||
dataSource = {
|
||||
getRows: async (params) => {
|
||||
const limit = params.endRow - params.startRow;
|
||||
const startIndex = params.startRow;
|
||||
|
||||
const query: SearchQuery = {
|
||||
albumArtistLimit: 0,
|
||||
albumArtistStartIndex: 0,
|
||||
albumLimit: 0,
|
||||
albumStartIndex: 0,
|
||||
query: searchQuery || ' ',
|
||||
songLimit: limit,
|
||||
songStartIndex: startIndex,
|
||||
};
|
||||
const query: SearchQuery = {
|
||||
albumArtistLimit: 0,
|
||||
albumArtistStartIndex: 0,
|
||||
albumLimit: 0,
|
||||
albumStartIndex: 0,
|
||||
query: searchQuery || ' ',
|
||||
songLimit: limit,
|
||||
songStartIndex: startIndex,
|
||||
};
|
||||
|
||||
const queryKey = queryKeys.search.list(server?.id || '', query);
|
||||
const queryKey = queryKeys.search.list(server?.id || '', query);
|
||||
|
||||
const res = await queryClient.fetchQuery(
|
||||
queryKey,
|
||||
async ({ signal }) =>
|
||||
api.controller.search({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
}),
|
||||
{ cacheTime: 1000 * 60 },
|
||||
);
|
||||
const res = await queryClient.fetchQuery(
|
||||
queryKey,
|
||||
async ({ signal }) =>
|
||||
api.controller.search({
|
||||
apiClientProps: {
|
||||
server,
|
||||
signal,
|
||||
},
|
||||
query,
|
||||
}),
|
||||
{ cacheTime: 1000 * 60 },
|
||||
);
|
||||
|
||||
if (!res) return;
|
||||
if (!res) return;
|
||||
|
||||
const items = res.songs || [];
|
||||
const numOfItems = items.length;
|
||||
const items = res.songs || [];
|
||||
const numOfItems = items.length;
|
||||
|
||||
let lastRow = -1;
|
||||
if (numOfItems < limit) {
|
||||
lastRow = startIndex + numOfItems;
|
||||
}
|
||||
let lastRow = -1;
|
||||
if (numOfItems < limit) {
|
||||
lastRow = startIndex + numOfItems;
|
||||
}
|
||||
|
||||
params.successCallback(items, lastRow);
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
params.successCallback(items, lastRow);
|
||||
},
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return dataSource;
|
||||
},
|
||||
[queryClient, server],
|
||||
);
|
||||
return dataSource;
|
||||
},
|
||||
[queryClient, server],
|
||||
);
|
||||
|
||||
return (
|
||||
<AnimatedPage key={`search-${navigationId}`}>
|
||||
<SearchHeader
|
||||
getDatasource={getDatasource}
|
||||
navigationId={navigationId}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<SearchContent
|
||||
key={`page-${itemType}`}
|
||||
getDatasource={getDatasource}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
</AnimatedPage>
|
||||
);
|
||||
return (
|
||||
<AnimatedPage key={`search-${navigationId}`}>
|
||||
<SearchHeader
|
||||
getDatasource={getDatasource}
|
||||
navigationId={navigationId}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
<SearchContent
|
||||
key={`page-${itemType}`}
|
||||
getDatasource={getDatasource}
|
||||
tableRef={tableRef}
|
||||
/>
|
||||
</AnimatedPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchRoute;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue