1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge pull request #6593 from viown/search-enhancements

Search Enhancements
This commit is contained in:
Bill Thornton 2025-03-26 18:54:09 -04:00 committed by GitHub
commit fcf344cea3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 713 additions and 546 deletions

View file

@ -1,68 +0,0 @@
import React, { type ChangeEvent, type FC, useCallback, useRef } from 'react';
import AlphaPicker from '../alphaPicker/AlphaPickerComponent';
import Input from 'elements/emby-input/Input';
import globalize from '../../lib/globalize';
import layoutManager from '../layoutManager';
import browser from '../../scripts/browser';
import 'material-design-icons-iconfont';
import '../../styles/flexstyles.scss';
import './searchfields.scss';
interface SearchFieldsProps {
query: string,
onSearch?: (query: string) => void
}
const SearchFields: FC<SearchFieldsProps> = ({
onSearch = () => { /* no-op */ },
query
}) => {
const inputRef = useRef<HTMLInputElement>(null);
const onAlphaPicked = useCallback((e: Event) => {
const value = (e as CustomEvent).detail.value;
const inputValue = inputRef.current?.value || '';
if (value === 'backspace') {
onSearch(inputValue.length ? inputValue.substring(0, inputValue.length - 1) : '');
} else {
onSearch(inputValue + value);
}
}, [onSearch]);
const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
onSearch(e.target.value);
}, [ onSearch ]);
return (
<div className='padded-left padded-right searchFields'>
<div className='searchFieldsInner flex align-items-center justify-content-center'>
<span className='searchfields-icon material-icons search' aria-hidden='true' />
<div
className='inputContainer flex-grow'
style={{ marginBottom: 0 }}
>
<Input
ref={inputRef}
id='searchTextInput'
className='searchfields-txtSearch'
type='text'
data-keyboard='true'
placeholder={globalize.translate('Search')}
autoComplete='off'
maxLength={40}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus
value={query}
onChange={onChange}
/>
</div>
</div>
{layoutManager.tv && !browser.tv
&& <AlphaPicker onAlphaPicked={onAlphaPicked} />
}
</div>
);
};
export default SearchFields;

View file

@ -1,60 +0,0 @@
import React, { type FC } from 'react';
import { Section, useSearchItems } from 'hooks/searchHook';
import globalize from '../../lib/globalize';
import Loading from '../loading/LoadingComponent';
import SearchResultsRow from './SearchResultsRow';
import { CardShape } from 'utils/card';
interface SearchResultsProps {
parentId?: string;
collectionType?: string;
query?: string;
}
/*
* React component to display search result rows for global search and library view search
*/
const SearchResults: FC<SearchResultsProps> = ({
parentId,
collectionType,
query
}) => {
const { isLoading, data } = useSearchItems(parentId, collectionType, query);
if (isLoading) return <Loading />;
if (!data?.length) {
return (
<div className='noItemsMessage centerMessage'>
{globalize.translate('SearchResultsEmpty', query)}
</div>
);
}
const renderSection = (section: Section, index: number) => {
return (
<SearchResultsRow
key={`${section.title}-${index}`}
title={globalize.translate(section.title)}
items={section.items}
cardOptions={{
shape: CardShape.AutoOverflow,
scalable: true,
showTitle: true,
overlayText: false,
centerText: true,
allowBottomPadding: false,
...section.cardOptions
}}
/>
);
};
return (
<div className={'searchResults, padded-top, padded-bottom-page'}>
{data.map((section, index) => renderSection(section, index))}
</div>
);
};
export default SearchResults;

View file

@ -1,44 +0,0 @@
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import React, { type FC, useEffect, useRef } from 'react';
import cardBuilder from '../cardbuilder/cardBuilder';
import type { CardOptions } from 'types/cardOptions';
import '../../elements/emby-scroller/emby-scroller';
import '../../elements/emby-itemscontainer/emby-itemscontainer';
// There seems to be some compatibility issues here between
// React and our legacy web components, so we need to inject
// them as an html string for now =/
const createScroller = ({ title = '' }) => ({
__html: `<h2 class="sectionTitle sectionTitle-cards focuscontainer-x padded-left padded-right">${title}</h2>
<div is="emby-scroller" data-horizontal="true" data-centerfocus="card" class="padded-top-focusscale padded-bottom-focusscale">
<div is="emby-itemscontainer" class="focuscontainer-x itemsContainer scrollSlider"></div>
</div>`
});
interface SearchResultsRowProps {
title?: string;
items?: BaseItemDto[];
cardOptions?: CardOptions;
}
const SearchResultsRow: FC<SearchResultsRowProps> = ({ title, items = [], cardOptions = {} }) => {
const element = useRef<HTMLDivElement>(null);
useEffect(() => {
cardBuilder.buildCards(items, {
itemsContainer: element.current?.querySelector('.itemsContainer'),
...cardOptions
});
}, [cardOptions, items]);
return (
<div
ref={element}
className='verticalSection'
dangerouslySetInnerHTML={createScroller({ title })}
/>
);
};
export default SearchResultsRow;

View file

@ -1,48 +0,0 @@
import React, { FunctionComponent } from 'react';
import Loading from 'components/loading/LoadingComponent';
import { appRouter } from '../router/appRouter';
import { useSearchSuggestions } from 'hooks/searchHook/useSearchSuggestions';
import globalize from 'lib/globalize';
import LinkButton from '../../elements/emby-button/LinkButton';
import '../../elements/emby-button/emby-button';
type SearchSuggestionsProps = {
parentId?: string | null;
};
const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ parentId }) => {
const { isLoading, data: suggestions } = useSearchSuggestions(parentId || undefined);
if (isLoading) return <Loading />;
return (
<div
className='verticalSection searchSuggestions'
style={{ textAlign: 'center' }}
>
<div>
<h2 className='sectionTitle padded-left padded-right'>
{globalize.translate('Suggestions')}
</h2>
</div>
<div className='searchSuggestionsList padded-left padded-right'>
{suggestions?.map(item => (
<div key={item.Id}>
<LinkButton
className='button-link'
style={{ display: 'inline-block', padding: '0.5em 1em' }}
href={appRouter.getRouteUrl(item)}
>
{item.Name}
</LinkButton>
</div>
))}
</div>
</div>
);
};
export default SearchSuggestions;

View file

@ -1,11 +0,0 @@
.searchFieldsInner {
max-width: 60em;
margin: 0 auto;
}
.searchfields-icon {
margin-bottom: 0.1em;
margin-right: 0.25em;
font-size: 2em;
align-self: flex-end;
}