fix(mods): remove pagination and fetch mods with sub fallback

Fixes #238
This commit is contained in:
en 2025-03-11 14:43:44 +00:00
parent b30d2191c7
commit 8f59875d53

View File

@ -1,6 +1,5 @@
import { ModFilter } from 'components/Filters/ModsFilter'
import { Pagination } from 'components/Pagination'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
createSearchParams,
useLoaderData,
@ -8,7 +7,7 @@ import {
} from 'react-router-dom'
import { LoadingSpinner } from '../../components/LoadingSpinner'
import { ModCard } from '../../components/ModCard'
import { MOD_FILTER_LIMIT } from '../../constants'
import { MOD_FILTER_LIMIT, T_TAG_VALUE } from '../../constants'
import {
useAppSelector,
useFilteredMods,
@ -20,77 +19,156 @@ import '../../styles/filters.css'
import '../../styles/pagination.css'
import '../../styles/search.css'
import '../../styles/styles.css'
import { FilterOptions, ModDetails } from '../../types'
import { DEFAULT_FILTER_OPTIONS, scrollIntoView } from 'utils'
import { FilterOptions, ModDetails, SortBy } from '../../types'
import {
DEFAULT_FILTER_OPTIONS,
extractModData,
isModDataComplete
} from 'utils'
import { SearchInput } from 'components/SearchInput'
import { ModsPageLoaderResult } from './loader'
import { FetchModsOptions } from 'contexts/NDKContext'
import {
NDKFilter,
NDKKind,
NDKSubscription,
NDKSubscriptionCacheUsage
} from '@nostr-dev-kit/ndk'
export const ModsPage = () => {
const scrollTargetRef = useRef<HTMLDivElement>(null)
const { repostList, muteLists, nsfwList } =
useLoaderData() as ModsPageLoaderResult
const { fetchMods } = useNDKContext()
const { ndk, fetchMods } = useNDKContext()
const [isFetching, setIsFetching] = useState(false)
const [mods, setMods] = useState<ModDetails[]>([])
const [isLoadMoreVisible, setIsLoadMoreVisible] = useState(true)
const [filterOptions] = useLocalStorage<FilterOptions>(
'filter',
DEFAULT_FILTER_OPTIONS
)
const [page, setPage] = useState(1)
const userState = useAppSelector((state) => state.user)
useEffect(() => {
setIsFetching(true)
fetchMods({ source: filterOptions.source })
.then((res) => {
setMods(res)
if (filterOptions.sort === SortBy.Latest) {
res.sort((a, b) => b.published_at - a.published_at)
} else if (filterOptions.sort === SortBy.Oldest) {
res.sort((a, b) => a.published_at - b.published_at)
}
setIsLoadMoreVisible(res.length >= MOD_FILTER_LIMIT)
setMods(res.slice(0, MOD_FILTER_LIMIT))
})
.finally(() => {
setIsFetching(false)
})
}, [filterOptions.source, fetchMods])
}, [filterOptions.source, fetchMods, filterOptions.sort])
const handleNext = useCallback(() => {
const lastMod: ModDetails | undefined = useMemo(() => {
// For the latest sort find oldest mod
// for the oldest sort find newest mod
return mods.reduce((prev, current) => {
if (!prev) return current
if (filterOptions.sort === SortBy.Latest) {
return current.edited_at < prev.edited_at ? current : prev
} else if (filterOptions.sort === SortBy.Oldest) {
return current.edited_at > prev.edited_at ? current : prev
}
return prev
}, undefined as ModDetails | undefined)
}, [mods, filterOptions.sort])
// Add missing mods to the list
useEffect(() => {
let sub: NDKSubscription
if (lastMod) {
const filter: NDKFilter = {
kinds: [NDKKind.Classified],
'#t': [T_TAG_VALUE]
}
if (filterOptions.sort === SortBy.Latest) {
filter.since = lastMod.edited_at + 1
} else if (filterOptions.sort === SortBy.Oldest) {
filter.until = lastMod.edited_at - 1
}
if (filterOptions.source === window.location.host) {
filter['#r'] = [window.location.host]
}
sub = ndk.subscribe(
filter,
{
closeOnEose: false,
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL
},
undefined,
{
onEvent: (ndkEvent) => {
setMods((prevMods) => {
// Skip if not valid
if (!isModDataComplete(ndkEvent)) {
return prevMods
}
// Skip existing
if (
prevMods.find(
(e) =>
e.id === ndkEvent.id ||
prevMods.findIndex((n) => n.id === ndkEvent.id) !== -1
)
) {
return prevMods
}
const newMod = extractModData(ndkEvent)
return [...prevMods, newMod]
})
}
}
)
}
return () => {
if (sub) sub.stop()
}
}, [filterOptions.sort, filterOptions.source, lastMod, ndk])
const handleLoadMore = useCallback(() => {
setIsFetching(true)
const until =
mods.length > 0 ? mods[mods.length - 1].published_at - 1 : undefined
const fetchModsOptions: FetchModsOptions = {
source: filterOptions.source
}
fetchMods({
source: filterOptions.source,
until
})
if (lastMod) {
if (filterOptions.sort === SortBy.Latest) {
fetchModsOptions.until = lastMod.edited_at - 1
} else if (filterOptions.sort === SortBy.Oldest) {
fetchModsOptions.since = lastMod.edited_at + 1
}
}
fetchMods(fetchModsOptions)
.then((res) => {
setMods(res)
setPage((prev) => prev + 1)
scrollIntoView(scrollTargetRef.current)
setMods((prevMods) => {
const newMods = res
const combinedMods = [...prevMods, ...newMods]
const uniqueMods = Array.from(
new Set(combinedMods.map((mod) => mod.id))
)
.map((id) => combinedMods.find((mod) => mod.id === id))
.filter((mod): mod is ModDetails => mod !== undefined)
setIsLoadMoreVisible(newMods.length >= MOD_FILTER_LIMIT)
return uniqueMods
})
})
.finally(() => {
setIsFetching(false)
})
}, [filterOptions.source, mods, fetchMods])
const handlePrev = useCallback(() => {
setIsFetching(true)
const since = mods.length > 0 ? mods[0].published_at + 1 : undefined
fetchMods({
source: filterOptions.source,
since
})
.then((res) => {
setMods(res)
setPage((prev) => prev - 1)
scrollIntoView(scrollTargetRef.current)
})
.finally(() => {
setIsFetching(false)
})
}, [filterOptions.source, mods, fetchMods])
}, [fetchMods, filterOptions, lastMod])
const filteredModList = useFilteredMods(
mods,
@ -121,12 +199,17 @@ export const ModsPage = () => {
</div>
</div>
<Pagination
page={page}
disabledNext={mods.length < MOD_FILTER_LIMIT}
handlePrev={handlePrev}
handleNext={handleNext}
/>
{!isFetching && isLoadMoreVisible && filteredModList.length > 0 && (
<div className='IBMSMListFeedLoadMore'>
<button
className='btn btnMain IBMSMListFeedLoadMoreBtn'
type='button'
onClick={handleLoadMore}
>
Load More
</button>
</div>
)}
</div>
</div>
</div>