mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
add list search links to command palette
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -32,6 +30,11 @@
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.items {
|
.items {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface CollapsibleCommandGroupProps {
|
|||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
heading: string;
|
heading: string;
|
||||||
onToggle?: () => void;
|
onToggle?: () => void;
|
||||||
subtitle?: string;
|
subtitle?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CollapsibleCommandGroup({
|
export function CollapsibleCommandGroup({
|
||||||
@@ -48,7 +48,7 @@ export function CollapsibleCommandGroup({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<Paper p="xs" radius="sm" withBorder>
|
<Paper p="sm" radius="sm" withBorder>
|
||||||
<div
|
<div
|
||||||
className={styles.heading}
|
className={styles.heading}
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, useNavigate } from 'react-router';
|
import { createSearchParams, generatePath, useNavigate } from 'react-router';
|
||||||
|
|
||||||
import { searchQueries } from '/@/renderer/features/search/api/search-api';
|
import { searchQueries } from '/@/renderer/features/search/api/search-api';
|
||||||
import { CollapsibleCommandGroup } from '/@/renderer/features/search/components/collapsible-command-group';
|
import { CollapsibleCommandGroup } from '/@/renderer/features/search/components/collapsible-command-group';
|
||||||
import { CommandItemSelectable } from '/@/renderer/features/search/components/command-item-selectable';
|
import { CommandItemSelectable } from '/@/renderer/features/search/components/command-item-selectable';
|
||||||
import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item';
|
import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item';
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { Box } from '/@/shared/components/box/box';
|
import { Box } from '/@/shared/components/box/box';
|
||||||
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
@@ -47,14 +51,50 @@ export function SearchAlbumArtistsSection({
|
|||||||
const showSection = isHome;
|
const showSection = isHome;
|
||||||
const numberOfResults = hasNextPage ? `${artists.length}+` : artists.length;
|
const numberOfResults = hasNextPage ? `${artists.length}+` : artists.length;
|
||||||
|
|
||||||
|
const handleGoToPage = useCallback(() => {
|
||||||
|
navigate(
|
||||||
|
{
|
||||||
|
pathname: AppRoute.LIBRARY_ALBUM_ARTISTS,
|
||||||
|
search: createSearchParams({
|
||||||
|
[FILTER_KEYS.SHARED.SEARCH_TERM]: debouncedQuery || query,
|
||||||
|
}).toString(),
|
||||||
|
},
|
||||||
|
{ state: { navigationId: nanoid() } },
|
||||||
|
);
|
||||||
|
onSelectResult();
|
||||||
|
}, [debouncedQuery, navigate, onSelectResult, query]);
|
||||||
|
|
||||||
if (!showSection) return null;
|
if (!showSection) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleCommandGroup
|
<CollapsibleCommandGroup
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
heading="Artists"
|
heading={t('entity.albumArtist', { count: 2, postProcess: 'titleCase' })}
|
||||||
onToggle={onToggle}
|
onToggle={onToggle}
|
||||||
subtitle={isFetched ? t('common.numberOfResults', { numberOfResults }) : undefined}
|
subtitle={
|
||||||
|
isFetched ? (
|
||||||
|
<>
|
||||||
|
{query ? (
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleGoToPage();
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
size="compact-xs"
|
||||||
|
variant="filled"
|
||||||
|
w="8rem"
|
||||||
|
>
|
||||||
|
{t('common.numberOfResults', { numberOfResults })}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Box p="md">
|
<Box p="md">
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, useNavigate } from 'react-router';
|
import { createSearchParams, generatePath, useNavigate } from 'react-router';
|
||||||
|
|
||||||
import { searchQueries } from '/@/renderer/features/search/api/search-api';
|
import { searchQueries } from '/@/renderer/features/search/api/search-api';
|
||||||
import { CollapsibleCommandGroup } from '/@/renderer/features/search/components/collapsible-command-group';
|
import { CollapsibleCommandGroup } from '/@/renderer/features/search/components/collapsible-command-group';
|
||||||
import { CommandItemSelectable } from '/@/renderer/features/search/components/command-item-selectable';
|
import { CommandItemSelectable } from '/@/renderer/features/search/components/command-item-selectable';
|
||||||
import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item';
|
import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item';
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { Box } from '/@/shared/components/box/box';
|
import { Box } from '/@/shared/components/box/box';
|
||||||
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
@@ -47,14 +51,50 @@ export function SearchAlbumsSection({
|
|||||||
const showSection = isHome;
|
const showSection = isHome;
|
||||||
const numberOfResults = hasNextPage ? `${albums.length}+` : albums.length;
|
const numberOfResults = hasNextPage ? `${albums.length}+` : albums.length;
|
||||||
|
|
||||||
|
const handleGoToPage = useCallback(() => {
|
||||||
|
navigate(
|
||||||
|
{
|
||||||
|
pathname: AppRoute.LIBRARY_ALBUMS,
|
||||||
|
search: createSearchParams({
|
||||||
|
[FILTER_KEYS.SHARED.SEARCH_TERM]: debouncedQuery || query,
|
||||||
|
}).toString(),
|
||||||
|
},
|
||||||
|
{ state: { navigationId: nanoid() } },
|
||||||
|
);
|
||||||
|
onSelectResult();
|
||||||
|
}, [debouncedQuery, navigate, onSelectResult, query]);
|
||||||
|
|
||||||
if (!showSection) return null;
|
if (!showSection) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleCommandGroup
|
<CollapsibleCommandGroup
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
heading="Albums"
|
heading={t('entity.album', { count: 2, postProcess: 'titleCase' })}
|
||||||
onToggle={onToggle}
|
onToggle={onToggle}
|
||||||
subtitle={isFetched ? t('common.numberOfResults', { numberOfResults }) : undefined}
|
subtitle={
|
||||||
|
isFetched ? (
|
||||||
|
<>
|
||||||
|
{query ? (
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleGoToPage();
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
size="compact-xs"
|
||||||
|
variant="filled"
|
||||||
|
w="8rem"
|
||||||
|
>
|
||||||
|
{t('common.numberOfResults', { numberOfResults })}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Box p="md">
|
<Box p="md">
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, useNavigate } from 'react-router';
|
import { createSearchParams, generatePath, useNavigate } from 'react-router';
|
||||||
|
|
||||||
import { searchQueries } from '/@/renderer/features/search/api/search-api';
|
import { searchQueries } from '/@/renderer/features/search/api/search-api';
|
||||||
import { CollapsibleCommandGroup } from '/@/renderer/features/search/components/collapsible-command-group';
|
import { CollapsibleCommandGroup } from '/@/renderer/features/search/components/collapsible-command-group';
|
||||||
import { CommandItemSelectable } from '/@/renderer/features/search/components/command-item-selectable';
|
import { CommandItemSelectable } from '/@/renderer/features/search/components/command-item-selectable';
|
||||||
import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item';
|
import { LibraryCommandItem } from '/@/renderer/features/search/components/library-command-item';
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { Box } from '/@/shared/components/box/box';
|
import { Box } from '/@/shared/components/box/box';
|
||||||
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
@@ -47,14 +51,50 @@ export function SearchSongsSection({
|
|||||||
const showSection = isHome;
|
const showSection = isHome;
|
||||||
const numberOfResults = hasNextPage ? `${songs.length}+` : songs.length;
|
const numberOfResults = hasNextPage ? `${songs.length}+` : songs.length;
|
||||||
|
|
||||||
|
const handleGoToPage = useCallback(() => {
|
||||||
|
navigate(
|
||||||
|
{
|
||||||
|
pathname: AppRoute.LIBRARY_SONGS,
|
||||||
|
search: createSearchParams({
|
||||||
|
[FILTER_KEYS.SHARED.SEARCH_TERM]: debouncedQuery || query,
|
||||||
|
}).toString(),
|
||||||
|
},
|
||||||
|
{ state: { navigationId: nanoid() } },
|
||||||
|
);
|
||||||
|
onSelectResult();
|
||||||
|
}, [debouncedQuery, navigate, onSelectResult, query]);
|
||||||
|
|
||||||
if (!showSection) return null;
|
if (!showSection) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleCommandGroup
|
<CollapsibleCommandGroup
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
heading="Tracks"
|
heading={t('entity.track', { count: 2, postProcess: 'titleCase' })}
|
||||||
onToggle={onToggle}
|
onToggle={onToggle}
|
||||||
subtitle={isFetched ? t('common.numberOfResults', { numberOfResults }) : undefined}
|
subtitle={
|
||||||
|
isFetched ? (
|
||||||
|
<>
|
||||||
|
{query ? (
|
||||||
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleGoToPage();
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
size="compact-xs"
|
||||||
|
variant="filled"
|
||||||
|
w="8rem"
|
||||||
|
>
|
||||||
|
{t('common.numberOfResults', { numberOfResults })}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Box p="md">
|
<Box p="md">
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
|
import { useLocation } from 'react-router';
|
||||||
|
|
||||||
import { SearchInput } from '/@/renderer/features/shared/components/search-input';
|
import { SearchInput } from '/@/renderer/features/shared/components/search-input';
|
||||||
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
import { useSearchTermFilter } from '/@/renderer/features/shared/hooks/use-search-term-filter';
|
||||||
|
|
||||||
|
function navigationIdFromState(state: unknown): string | undefined {
|
||||||
|
if (state && typeof state === 'object' && 'navigationId' in state) {
|
||||||
|
const id = (state as { navigationId: unknown }).navigationId;
|
||||||
|
return typeof id === 'string' ? id : undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export const ListSearchInput = () => {
|
export const ListSearchInput = () => {
|
||||||
const { searchTerm, setSearchTerm } = useSearchTermFilter();
|
const { searchTerm, setSearchTerm } = useSearchTermFilter();
|
||||||
|
const { state } = useLocation();
|
||||||
|
const navigationId = navigationIdFromState(state);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchInput
|
<SearchInput
|
||||||
defaultValue={searchTerm}
|
defaultValue={searchTerm}
|
||||||
|
key={navigationId ?? 'list-search-input'}
|
||||||
onChange={(e) => setSearchTerm(e.target.value || null)}
|
onChange={(e) => setSearchTerm(e.target.value || null)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user