mirror of
https://git.vectorsigma.ru/public/tubearchivist.git
synced 2026-08-04 19:09:29 +00:00
173 lines
5.8 KiB
TypeScript
173 lines
5.8 KiB
TypeScript
import { useSearchParams } from 'react-router-dom';
|
|
import { useEffect, useState } from 'react';
|
|
import loadSearch, { SearchResultsType } from '../api/loader/loadSearch';
|
|
import VideoList from '../components/VideoList';
|
|
import ChannelList from '../components/ChannelList';
|
|
import PlaylistList from '../components/PlaylistList';
|
|
import SubtitleList from '../components/SubtitleList';
|
|
import { ViewStyles } from '../configuration/constants/ViewStyle';
|
|
import EmbeddableVideoPlayer from '../components/EmbeddableVideoPlayer';
|
|
import SearchExampleQueries from '../components/SearchExampleQueries';
|
|
import { useUserConfigStore } from '../stores/UserConfigStore';
|
|
import { ApiResponseType } from '../functions/APIClient';
|
|
|
|
const EmptySearchResponse: ApiResponseType<SearchResultsType> = {
|
|
data: {
|
|
results: {
|
|
video_results: [],
|
|
channel_results: [],
|
|
playlist_results: [],
|
|
fulltext_results: [],
|
|
},
|
|
queryType: 'simple',
|
|
},
|
|
error: undefined,
|
|
status: 200,
|
|
};
|
|
|
|
const Search = () => {
|
|
const { userConfig } = useUserConfigStore();
|
|
const [searchParams] = useSearchParams();
|
|
const videoId = searchParams.get('videoId');
|
|
|
|
const viewVideos = userConfig.view_style_home;
|
|
const viewChannels = userConfig.view_style_channel;
|
|
const viewPlaylists = userConfig.view_style_playlist;
|
|
const gridItems = userConfig.grid_items || 3;
|
|
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [debouncedSearchTerm, setDebouncedSearchTerm] = useState<string>('');
|
|
const [searchResults, setSearchResults] = useState<ApiResponseType<SearchResultsType>>();
|
|
|
|
const [refresh, setRefresh] = useState(false);
|
|
|
|
const { data: searchResultsData } = searchResults ?? {};
|
|
|
|
const videoList = searchResultsData?.results.video_results;
|
|
const channelList = searchResultsData?.results.channel_results;
|
|
const playlistList = searchResultsData?.results.playlist_results;
|
|
const fulltextList = searchResultsData?.results.fulltext_results;
|
|
const queryType = searchResultsData?.queryType;
|
|
|
|
const hasSearchQuery = searchTerm.length > 0;
|
|
const hasVideos = Number(videoList?.length) > 0;
|
|
const hasChannels = Number(channelList?.length) > 0;
|
|
const hasPlaylist = Number(playlistList?.length) > 0;
|
|
const hasFulltext = Number(fulltextList?.length) > 0;
|
|
|
|
const isSimpleQuery = queryType === 'simple';
|
|
const isVideoQuery = queryType === 'video' || isSimpleQuery;
|
|
const isChannelQuery = queryType === 'channel' || isSimpleQuery;
|
|
const isPlaylistQuery = queryType === 'playlist' || isSimpleQuery;
|
|
const isFullTextQuery = queryType === 'full' || isSimpleQuery;
|
|
|
|
const isGridView = viewVideos === ViewStyles.grid;
|
|
const gridView = isGridView ? `boxed-${gridItems}` : '';
|
|
const gridViewGrid = isGridView ? `grid-${gridItems}` : '';
|
|
|
|
useEffect(() => {
|
|
const handler = setTimeout(() => {
|
|
setDebouncedSearchTerm(searchTerm);
|
|
}, 300);
|
|
|
|
return () => {
|
|
clearTimeout(handler);
|
|
};
|
|
}, [searchTerm]);
|
|
|
|
useEffect(() => {
|
|
if (debouncedSearchTerm.trim() !== '') {
|
|
fetchResults(debouncedSearchTerm);
|
|
} else {
|
|
setSearchResults(EmptySearchResponse);
|
|
}
|
|
}, [debouncedSearchTerm, refresh, videoId]);
|
|
|
|
const fetchResults = async (searchQuery: string) => {
|
|
const searchResults = await loadSearch(searchQuery);
|
|
|
|
setSearchResults(searchResults);
|
|
setRefresh(false);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<title>TubeArchivist</title>
|
|
|
|
<EmbeddableVideoPlayer videoId={videoId} />
|
|
|
|
<div className={`boxed-content ${gridView}`}>
|
|
<div className="title-bar">
|
|
<h1>Search your Archive</h1>
|
|
</div>
|
|
<div className="multi-search-box">
|
|
<div>
|
|
<input
|
|
type="text"
|
|
autoFocus
|
|
autoComplete="off"
|
|
value={searchTerm}
|
|
onChange={event => {
|
|
setSearchTerm(event.currentTarget.value);
|
|
}}
|
|
onKeyDown={event => {
|
|
if (event.key === 'Enter') {
|
|
fetchResults(searchTerm);
|
|
}
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div id="multi-search-results">
|
|
{hasSearchQuery && isVideoQuery && (
|
|
<div className="multi-search-result">
|
|
<h2>Video Results</h2>
|
|
<div id="video-results" className={`video-list ${viewVideos} ${gridViewGrid}`}>
|
|
<VideoList
|
|
videoList={videoList}
|
|
viewLayout={viewVideos}
|
|
refreshVideoList={setRefresh}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{hasSearchQuery && isChannelQuery && (
|
|
<div className="multi-search-result">
|
|
<h2>Channel Results</h2>
|
|
<div id="channel-results" className={`channel-list ${viewChannels} ${gridViewGrid}`}>
|
|
<ChannelList channelList={channelList} refreshChannelList={setRefresh} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{hasSearchQuery && isPlaylistQuery && (
|
|
<div className="multi-search-result">
|
|
<h2>Playlist Results</h2>
|
|
<div
|
|
id="playlist-results"
|
|
className={`playlist-list ${viewPlaylists} ${gridViewGrid}`}
|
|
>
|
|
<PlaylistList playlistList={playlistList} setRefresh={setRefresh} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{hasSearchQuery && isFullTextQuery && (
|
|
<div className="multi-search-result">
|
|
<h2>Fulltext Results</h2>
|
|
<div id="fulltext-results" className="video-list list">
|
|
<SubtitleList subtitleList={fulltextList} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{!hasVideos && !hasChannels && !hasPlaylist && !hasFulltext && <SearchExampleQueries />}
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Search;
|