millelibri/frontend/src/components/Search.tsx

232 lines
6.3 KiB
TypeScript

import { Button, GridItem, Icon, SimpleGrid, Flex, Box, Card } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import MediaQuery from 'react-responsive'
import {
TbBook2,
TbBuilding,
TbFileDescription,
TbHash,
TbReportSearch,
TbUserCircle
} from 'react-icons/tb';
import search, { Book } from '../scripts/searcher';
import { IoLanguage } from 'react-icons/io5';
import { IoShareSocialOutline } from 'react-icons/io5';
import SearchInput from './SearchInput';
import { useDebounceEffect } from 'ahooks';
import { useTranslation } from 'react-i18next';
import SearchLanguage from './SearchLanguage';
import { MEDIA_QUERY_DESKTOP_STARTS, MEDIA_QUERY_MOBILE_ENDS } from '../constants/mediaquery';
import CopyToClipboardButton from './CopyToClipboardButton';
function constructQuery(parts: Record<string, string>): string {
return Object.keys(parts)
.map((key) =>
parts[key]
.split(' ')
.filter((s) => s !== '')
.map((s) => `${key}:"${s}"`)
)
.flat()
.join('');
}
export interface SearchProps {
setBooks: (books: Book[]) => void;
}
const Search: React.FC<SearchProps> = ({ setBooks }) => {
const { t } = useTranslation();
const [title, setTitle] = useState<string>('');
const [author, setAuthor] = useState<string>('');
const [publisher, setPublisher] = useState<string>('');
const [extension, setExtension] = useState<string>('');
const [language, setLanguage] = useState<string>('');
const [isbn, setISBN] = useState<string>('');
const [complexQuery, setComplexQuery] = useState<string>('');
const [showLanguageDropdown, setShowLanguageDropdown] = useState<boolean>(true)
const [booksFromJsonArchive, setBooksFromJsonArchive] = useState<Book[]>([])
useEffect(() => {
const params = new Proxy(new URLSearchParams(decodeURIComponent(window.location.search)), {
//@ts-ignore
get: (searchParams, prop) => searchParams.get(prop),
});
//@ts-ignore
params.title && setTitle(String(params.title))
//@ts-ignore
params.author && setAuthor(String(params.author))
//@ts-ignore
if (params.language) {
//@ts-ignore
setLanguage(String(params.language))
setShowLanguageDropdown(false)
}
}, [])
const handleLanguageChange = (language: string) => {
if (language == 'input') {
setShowLanguageDropdown(false)
} else {
setLanguage(language)
}
}
const handleLanguageReset = () => {
setShowLanguageDropdown(true)
setLanguage('')
}
const hasAnySearchBeenMade = () => {
return (
title !== ''
|| author !== ''
|| language !== ''
|| publisher !== ''
|| extension !== ''
|| isbn !== ''
)
}
const copyToClipboard = () => {
const searchBase = `${window.location.protocol}//${window.location.host}/?`;
const searchQuery = encodeURIComponent(`title=${title}&author=${author}&language=${language}&publisher=${publisher}&isbn=${isbn}&extension=${extension}`)
navigator.clipboard.writeText(`${searchBase}${searchQuery}`).then(() => {
//console.log('Async: Copying to clipboard was successful!');
}, (err) => {
//console.error('Async: Could not copy text: ', err);
});
}
const filterBooks = (books: Book[]) => {
if (!hasAnySearchBeenMade()) {
return []
}
return books.filter((x) => {
let matchTitle = true;
let matchAuthor = true;
let matchPublisher = true;
let matchExtension = true;
let matchLanguage = true;
if (title !== '') {
matchTitle = x.title.includes(title)
}
if (author !== '') {
matchAuthor = x.author.includes(author)
}
if (publisher !== '' && x.publisher) {
matchPublisher = x.publisher.includes(publisher)
}
if (extension !== '') {
matchExtension = x.extension.includes(extension)
}
if (language !== '') {
matchLanguage = x.language.includes(language)
}
return matchTitle && matchAuthor && matchPublisher && matchExtension && matchLanguage
})
}
useDebounceEffect(
() => {
const query = complexQuery
? complexQuery
: constructQuery({ title, author, publisher, extension, language, isbn });
search(query, 100).then((books) => {
setBooks(complexQuery ? books : books.concat(filterBooks(booksFromJsonArchive)));
});
},
[title, author, publisher, extension, language, isbn, complexQuery],
{ wait: 300 }
);
return (
<>
<SimpleGrid
columns={{ sm: 1, md: 2, lg: 3 }}
spacing={{ base: 2, md: 4 }}
px={{ base: 4, md: 8 }}
>
<SearchInput
icon={<Icon as={TbBook2} />}
placeholder={t('book.title')}
value={title}
onChange={setTitle}
/>
<SearchInput
icon={<Icon as={TbUserCircle} />}
placeholder={t('book.author')}
value={author}
onChange={setAuthor}
/>
<SearchInput
icon={<Icon as={TbBuilding} />}
placeholder={t('book.publisher')}
value={publisher}
onChange={setPublisher}
/>
<SearchInput
icon={<Icon as={TbFileDescription} />}
placeholder={t('book.extension')}
value={extension}
onChange={setExtension}
/>
{!showLanguageDropdown && (<SearchInput
icon={<Icon as={IoLanguage} />}
placeholder={t('input.select_language')}
value={language}
onChange={handleLanguageChange}
onClear={handleLanguageReset}
/>)}
{showLanguageDropdown && (<SearchLanguage
icon={<Icon as={IoLanguage} />}
placeholder={t('input.select_language')}
value={language}
onChange={handleLanguageChange}
/>)}
<SearchInput
icon={<Icon as={TbHash} />}
placeholder={t('book.isbn')}
value={isbn}
onChange={setISBN}
/>
<GridItem colSpan={{ sm: 1, md: 2, lg: 3 }}>
<SearchInput
icon={<Icon as={TbReportSearch} />}
placeholder={t('search.complex')}
value={complexQuery}
onChange={setComplexQuery}
/>
</GridItem>
</SimpleGrid>
<CopyToClipboardButton
show={hasAnySearchBeenMade()}
onClick={copyToClipboard} />
</>
);
};
export default Search;