import React, { useEffect, useRef, useState } from 'react' import { useCookies } from 'react-cookie' import { useRouter } from 'next/router' import { useSnapshot } from 'valtio' import { useTranslation } from 'react-i18next' import InfiniteScroll from 'react-infinite-scroll-component' import { appState } from '~utils/appState' import api from '~utils/api' import * as Dialog from '@radix-ui/react-dialog' import CharacterSearchFilterBar from '~components/CharacterSearchFilterBar' import WeaponSearchFilterBar from '~components/WeaponSearchFilterBar' import SummonSearchFilterBar from '~components/SummonSearchFilterBar' import CharacterResult from '~components/CharacterResult' import WeaponResult from '~components/WeaponResult' import SummonResult from '~components/SummonResult' import './index.scss' import CrossIcon from '~public/icons/Cross.svg' import cloneDeep from 'lodash.clonedeep' interface Props { send: (object: Character | Weapon | Summon, position: number) => any placeholderText: string fromPosition: number object: 'weapons' | 'characters' | 'summons', children: React.ReactNode } const SearchModal = (props: Props) => { // Set up snapshot of app state let { grid, search } = useSnapshot(appState) // Set up router const router = useRouter() const locale = router.locale // Set up translation const { t } = useTranslation('common') // Set up cookies const [cookies, setCookies] = useCookies() let searchInput = React.createRef() let scrollContainer = React.createRef() const [firstLoad, setFirstLoad] = useState(true) const [objects, setObjects] = useState<{[id: number]: GridCharacter | GridWeapon | GridSummon}>() const [filters, setFilters] = useState<{ [key: string]: number[] }>() const [open, setOpen] = useState(false) const [query, setQuery] = useState('') const [results, setResults] = useState<(Weapon | Summon | Character)[]>([]) // Pagination states const [recordCount, setRecordCount] = useState(0) const [currentPage, setCurrentPage] = useState(1) const [totalPages, setTotalPages] = useState(1) useEffect(() => { setObjects(grid[props.object]) }, [grid, props.object]) useEffect(() => { if (searchInput.current) searchInput.current.focus() }, [searchInput]) function inputChanged(event: React.ChangeEvent) { const text = event.target.value if (text.length) { setQuery(text) } else { setQuery('') } } function fetchResults({ replace = false }: { replace?: boolean }) { api.search({ object: props.object, query: query, filters: filters, locale: locale, page: currentPage }).then(response => { setTotalPages(response.data.total_pages) setRecordCount(response.data.count) if (replace) { replaceResults(response.data.count, response.data.results) } else { appendResults(response.data.results) } }).catch(error => { console.error(error) }) } function replaceResults(count: number, list: Weapon[] | Summon[] | Character[]) { if (count > 0) { setResults(list) } else { setResults([]) } } function appendResults(list: Weapon[] | Summon[] | Character[]) { setResults([...results, ...list]) } function storeRecentResult(result: Character | Weapon | Summon) { const key = `recent_${props.object}` let recents: Character[] | Weapon[] | Summon[] = [] if (props.object === "weapons") { recents = cloneDeep(cookies[key] as Weapon[]) || [] if (!recents.find(item => item.granblue_id === result.granblue_id)) { recents.unshift(result as Weapon) } } else if (props.object === "summons") { recents = cloneDeep(cookies[key] as Summon[]) || [] if (!recents.find(item => item.granblue_id === result.granblue_id)) { recents.unshift(result as Summon) } } if (recents && recents.length > 5) recents.pop() setCookies(`recent_${props.object}`, recents, { path: '/' }) sendData(result) } function sendData(result: Character | Weapon | Summon) { props.send(result, props.fromPosition) openChange() } function receiveFilters(filters: { [key: string]: number[] }) { setCurrentPage(1) setResults([]) setFilters(filters) } useEffect(() => { // Current page changed if (open && currentPage > 1) { fetchResults({ replace: false }) } else if (open && currentPage == 1) { fetchResults({ replace: true }) } }, [currentPage]) useEffect(() => { // Filters changed const key = `recent_${props.object}` if (open) { if (firstLoad && cookies[key] && cookies[key].length > 0) { setResults(cookies[key]) setRecordCount(cookies[key].length) setFirstLoad(false) } else { setCurrentPage(1) fetchResults({ replace: true }) } } }, [filters]) useEffect(() => { // Query changed if (open && query.length != 1) { setCurrentPage(1) fetchResults({ replace: true }) } }, [query]) function renderResults() { let jsx switch(props.object) { case 'weapons': jsx = renderWeaponSearchResults() break case 'summons': jsx = renderSummonSearchResults(results) break case 'characters': jsx = renderCharacterSearchResults(results) break } return ( 0) ? results.length : 0} next={ () => setCurrentPage(currentPage + 1) } hasMore={totalPages > currentPage} scrollableTarget="Results" loader={
Loading...
}> {jsx}
) } function renderWeaponSearchResults() { let jsx: React.ReactNode const castResults: Weapon[] = results as Weapon[] if (castResults && Object.keys(castResults).length > 0) { jsx = castResults.map((result: Weapon) => { return { storeRecentResult(result) }} /> }) } return jsx } function renderSummonSearchResults(results: { [key: string]: any }) { let jsx: React.ReactNode const castResults: Summon[] = results as Summon[] if (castResults && Object.keys(castResults).length > 0) { jsx = castResults.map((result: Summon) => { return { storeRecentResult(result) }} /> }) } return jsx } function renderCharacterSearchResults(results: { [key: string]: any }) { let jsx: React.ReactNode const castResults: Character[] = results as Character[] if (castResults && Object.keys(castResults).length > 0) { jsx = castResults.map((result: Character) => { return { storeRecentResult(result) }} /> }) } return jsx } function openChange() { if (open) { setQuery('') setFirstLoad(true) setResults([]) setRecordCount(0) setCurrentPage(1) setOpen(false) } else { setOpen(true) } } return ( {props.children}
{t('search.result_count', { "record_count": recordCount })}
{ (open) ? renderResults() : ''}
) } export default SearchModal