2024-10-14 19:20:43 +05:00
|
|
|
import {
|
|
|
|
NDKFilter,
|
|
|
|
NDKKind,
|
|
|
|
NDKSubscriptionCacheUsage
|
|
|
|
} from '@nostr-dev-kit/ndk'
|
2024-09-18 21:42:50 +05:00
|
|
|
import { ModCard } from 'components/ModCard'
|
2024-10-07 15:45:21 +05:00
|
|
|
import { ModFilter } from 'components/ModsFilter'
|
2024-09-18 21:42:50 +05:00
|
|
|
import { PaginationWithPageNumbers } from 'components/Pagination'
|
2024-10-29 09:35:39 +01:00
|
|
|
import { SearchInput } from 'components/SearchInput'
|
2024-09-18 21:42:50 +05:00
|
|
|
import { MAX_MODS_PER_PAGE, T_TAG_VALUE } from 'constants.ts'
|
2024-10-07 15:45:21 +05:00
|
|
|
import {
|
|
|
|
useAppSelector,
|
|
|
|
useFilteredMods,
|
|
|
|
useMuteLists,
|
2024-10-14 19:20:43 +05:00
|
|
|
useNDKContext,
|
2024-10-07 15:45:21 +05:00
|
|
|
useNSFWList
|
|
|
|
} from 'hooks'
|
2024-10-29 09:35:39 +01:00
|
|
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
|
|
import { useParams, useSearchParams } from 'react-router-dom'
|
2024-10-07 15:45:21 +05:00
|
|
|
import {
|
|
|
|
FilterOptions,
|
|
|
|
ModDetails,
|
|
|
|
ModeratedFilter,
|
|
|
|
NSFWFilter,
|
|
|
|
SortBy
|
|
|
|
} from 'types'
|
2024-10-21 15:21:09 +02:00
|
|
|
import { extractModData, isModDataComplete, scrollIntoView } from 'utils'
|
2024-09-18 21:42:50 +05:00
|
|
|
|
|
|
|
export const GamePage = () => {
|
2024-10-21 15:21:09 +02:00
|
|
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
2024-09-18 21:42:50 +05:00
|
|
|
const params = useParams()
|
|
|
|
const { name: gameName } = params
|
2024-10-14 19:20:43 +05:00
|
|
|
const { ndk } = useNDKContext()
|
2024-09-18 21:42:50 +05:00
|
|
|
const muteLists = useMuteLists()
|
2024-10-07 15:45:21 +05:00
|
|
|
const nsfwList = useNSFWList()
|
2024-09-18 21:42:50 +05:00
|
|
|
|
|
|
|
const [filterOptions, setFilterOptions] = useState<FilterOptions>({
|
2024-10-07 15:45:21 +05:00
|
|
|
sort: SortBy.Latest,
|
|
|
|
nsfw: NSFWFilter.Hide_NSFW,
|
|
|
|
source: window.location.host,
|
|
|
|
moderated: ModeratedFilter.Moderated
|
2024-09-18 21:42:50 +05:00
|
|
|
})
|
|
|
|
const [mods, setMods] = useState<ModDetails[]>([])
|
|
|
|
|
|
|
|
const [currentPage, setCurrentPage] = useState(1)
|
|
|
|
|
|
|
|
const userState = useAppSelector((state) => state.user)
|
|
|
|
|
2024-10-29 09:35:39 +01:00
|
|
|
// Search
|
|
|
|
const searchTermRef = useRef<HTMLInputElement>(null)
|
|
|
|
const [searchParams, setSearchParams] = useSearchParams()
|
|
|
|
const [searchTerm, setSearchTerm] = useState(searchParams.get('q') || '')
|
|
|
|
|
|
|
|
const handleSearch = () => {
|
|
|
|
const value = searchTermRef.current?.value || '' // Access the input value from the ref
|
|
|
|
setSearchTerm(value)
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
searchParams.set('q', value)
|
|
|
|
} else {
|
|
|
|
searchParams.delete('q')
|
|
|
|
}
|
|
|
|
|
|
|
|
setSearchParams(searchParams, {
|
|
|
|
replace: true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle "Enter" key press inside the input
|
|
|
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
|
|
if (event.key === 'Enter') {
|
|
|
|
handleSearch()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const filteredMods = useMemo(() => {
|
|
|
|
const filterSourceFn = (mod: ModDetails) => {
|
|
|
|
if (filterOptions.source === window.location.host) {
|
|
|
|
return mod.rTag === filterOptions.source
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// If search term is missing, only filter by sources
|
|
|
|
if (searchTerm === '') return mods.filter(filterSourceFn)
|
|
|
|
|
|
|
|
const lowerCaseSearchTerm = searchTerm.toLowerCase()
|
|
|
|
|
|
|
|
const filterFn = (mod: ModDetails) =>
|
|
|
|
mod.title.toLowerCase().includes(lowerCaseSearchTerm) ||
|
|
|
|
mod.game.toLowerCase().includes(lowerCaseSearchTerm) ||
|
|
|
|
mod.summary.toLowerCase().includes(lowerCaseSearchTerm) ||
|
|
|
|
mod.body.toLowerCase().includes(lowerCaseSearchTerm) ||
|
|
|
|
mod.tags.findIndex((tag) =>
|
|
|
|
tag.toLowerCase().includes(lowerCaseSearchTerm)
|
|
|
|
) > -1
|
|
|
|
|
|
|
|
return mods.filter(filterFn).filter(filterSourceFn)
|
|
|
|
}, [filterOptions.source, mods, searchTerm])
|
|
|
|
|
|
|
|
const filteredModList = useFilteredMods(
|
|
|
|
filteredMods,
|
2024-10-07 15:45:21 +05:00
|
|
|
userState,
|
|
|
|
filterOptions,
|
|
|
|
nsfwList,
|
2024-09-18 21:42:50 +05:00
|
|
|
muteLists
|
2024-10-07 15:45:21 +05:00
|
|
|
)
|
2024-09-18 21:42:50 +05:00
|
|
|
|
|
|
|
// Pagination logic
|
2024-10-29 09:35:39 +01:00
|
|
|
const totalGames = filteredModList.length
|
2024-09-18 21:42:50 +05:00
|
|
|
const totalPages = Math.ceil(totalGames / MAX_MODS_PER_PAGE)
|
|
|
|
const startIndex = (currentPage - 1) * MAX_MODS_PER_PAGE
|
|
|
|
const endIndex = startIndex + MAX_MODS_PER_PAGE
|
2024-10-29 09:35:39 +01:00
|
|
|
const currentMods = filteredModList.slice(startIndex, endIndex)
|
2024-09-18 21:42:50 +05:00
|
|
|
|
|
|
|
const handlePageChange = (page: number) => {
|
|
|
|
if (page >= 1 && page <= totalPages) {
|
2024-10-21 15:21:09 +02:00
|
|
|
scrollIntoView(scrollTargetRef.current)
|
2024-09-18 21:42:50 +05:00
|
|
|
setCurrentPage(page)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
2024-10-14 19:20:43 +05:00
|
|
|
const filter: NDKFilter = {
|
|
|
|
kinds: [NDKKind.Classified],
|
2024-09-18 21:42:50 +05:00
|
|
|
'#t': [T_TAG_VALUE]
|
|
|
|
}
|
|
|
|
|
2024-10-14 19:20:43 +05:00
|
|
|
const subscription = ndk.subscribe(filter, {
|
|
|
|
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL,
|
|
|
|
closeOnEose: true
|
|
|
|
})
|
|
|
|
|
|
|
|
subscription.on('event', (ndkEvent) => {
|
|
|
|
if (isModDataComplete(ndkEvent)) {
|
|
|
|
const mod = extractModData(ndkEvent)
|
|
|
|
if (mod.game === gameName)
|
|
|
|
setMods((prev) => {
|
|
|
|
if (prev.find((e) => e.aTag === mod.aTag)) return [...prev]
|
2024-09-18 21:42:50 +05:00
|
|
|
|
2024-10-14 19:20:43 +05:00
|
|
|
return [...prev, mod]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2024-09-18 21:42:50 +05:00
|
|
|
|
2024-10-14 19:20:43 +05:00
|
|
|
subscription.start()
|
2024-09-18 21:42:50 +05:00
|
|
|
|
2024-10-14 19:20:43 +05:00
|
|
|
// Cleanup function to stop subscription
|
2024-09-18 21:42:50 +05:00
|
|
|
return () => {
|
2024-10-14 19:20:43 +05:00
|
|
|
subscription.stop()
|
2024-09-18 21:42:50 +05:00
|
|
|
}
|
2024-10-14 19:20:43 +05:00
|
|
|
}, [gameName, ndk])
|
2024-09-18 21:42:50 +05:00
|
|
|
|
|
|
|
if (!gameName) return null
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div className='InnerBodyMain'>
|
|
|
|
<div className='ContainerMain'>
|
2024-10-21 15:21:09 +02:00
|
|
|
<div
|
|
|
|
className='IBMSecMainGroup IBMSecMainGroupAlt'
|
|
|
|
ref={scrollTargetRef}
|
|
|
|
>
|
2024-09-18 21:42:50 +05:00
|
|
|
<div className='IBMSecMain'>
|
|
|
|
<div className='SearchMainWrapper'>
|
|
|
|
<div className='IBMSMTitleMain'>
|
|
|
|
<h2 className='IBMSMTitleMainHeading'>
|
|
|
|
Game:
|
|
|
|
<span className='IBMSMTitleMainHeadingSpan'>
|
|
|
|
{gameName}
|
|
|
|
</span>
|
2024-10-29 09:35:39 +01:00
|
|
|
{searchTerm !== '' && (
|
|
|
|
<>
|
|
|
|
—
|
|
|
|
<span className='IBMSMTitleMainHeadingSpan'>
|
|
|
|
{searchTerm}
|
|
|
|
</span>
|
|
|
|
</>
|
|
|
|
)}
|
2024-09-18 21:42:50 +05:00
|
|
|
</h2>
|
|
|
|
</div>
|
2024-10-29 09:35:39 +01:00
|
|
|
<SearchInput
|
|
|
|
handleKeyDown={handleKeyDown}
|
|
|
|
handleSearch={handleSearch}
|
|
|
|
ref={searchTermRef}
|
|
|
|
/>
|
2024-09-18 21:42:50 +05:00
|
|
|
</div>
|
|
|
|
</div>
|
2024-10-07 15:45:21 +05:00
|
|
|
<ModFilter
|
2024-09-18 21:42:50 +05:00
|
|
|
filterOptions={filterOptions}
|
|
|
|
setFilterOptions={setFilterOptions}
|
|
|
|
/>
|
|
|
|
<div className='IBMSecMain IBMSMListWrapper'>
|
|
|
|
<div className='IBMSMList'>
|
2024-09-23 20:58:50 +05:00
|
|
|
{currentMods.map((mod) => (
|
|
|
|
<ModCard key={mod.id} {...mod} />
|
|
|
|
))}
|
2024-09-18 21:42:50 +05:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<PaginationWithPageNumbers
|
|
|
|
currentPage={currentPage}
|
|
|
|
totalPages={totalPages}
|
|
|
|
handlePageChange={handlePageChange}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|