chore(git): merge pull request #239 from issues/238-mods-fetching-fallback into staging
All checks were successful
Release to Staging / build_and_release (push) Successful in 1m11s
All checks were successful
Release to Staging / build_and_release (push) Successful in 1m11s
Reviewed-on: #239
This commit is contained in:
commit
7783371297
@ -31,7 +31,7 @@ import {
|
|||||||
timeout
|
timeout
|
||||||
} from 'utils'
|
} from 'utils'
|
||||||
|
|
||||||
type FetchModsOptions = {
|
export type FetchModsOptions = {
|
||||||
source?: string
|
source?: string
|
||||||
until?: number
|
until?: number
|
||||||
since?: number
|
since?: number
|
||||||
@ -170,16 +170,25 @@ export const NDKContextProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
const filter: NDKFilter = {
|
const filter: NDKFilter = {
|
||||||
kinds: [NDKKind.Classified], // Specify the kind of events to fetch
|
kinds: [NDKKind.Classified], // Specify the kind of events to fetch
|
||||||
limit: limit || MOD_FILTER_LIMIT, // Limit the number of events fetched to 20
|
limit: limit || MOD_FILTER_LIMIT, // Limit the number of events fetched to 20
|
||||||
'#t': [T_TAG_VALUE],
|
'#t': [T_TAG_VALUE]
|
||||||
until, // Optional filter to fetch events until this timestamp
|
|
||||||
since, // Optional filter to fetch events from this timestamp
|
|
||||||
authors: author ? [author] : undefined // Optional filter to fetch events from only this author
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the source matches the current window location, add a filter condition
|
// If the source matches the current window location, add a filter condition
|
||||||
if (source === window.location.host) {
|
if (source === window.location.host) {
|
||||||
filter['#r'] = [window.location.host] // Add a tag filter for the current host
|
filter['#r'] = [window.location.host] // Add a tag filter for the current host
|
||||||
}
|
}
|
||||||
|
// Optional filter to fetch events until this timestamp
|
||||||
|
if (until) {
|
||||||
|
filter['until'] = until
|
||||||
|
}
|
||||||
|
// Optional filter to fetch events from this timestamp
|
||||||
|
if (since) {
|
||||||
|
filter['since'] = since
|
||||||
|
}
|
||||||
|
// Optional filter to fetch events from only this author
|
||||||
|
if (author) {
|
||||||
|
filter['authors'] = [author]
|
||||||
|
}
|
||||||
|
|
||||||
return ndk
|
return ndk
|
||||||
.fetchEvents(filter, {
|
.fetchEvents(filter, {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { ModFilter } from 'components/Filters/ModsFilter'
|
import { ModFilter } from 'components/Filters/ModsFilter'
|
||||||
import { Pagination } from 'components/Pagination'
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
||||||
import {
|
import {
|
||||||
createSearchParams,
|
createSearchParams,
|
||||||
useLoaderData,
|
useLoaderData,
|
||||||
@ -8,7 +7,7 @@ import {
|
|||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { ModCard } from '../../components/ModCard'
|
import { ModCard } from '../../components/ModCard'
|
||||||
import { MOD_FILTER_LIMIT } from '../../constants'
|
import { MOD_FILTER_LIMIT, T_TAG_VALUE } from '../../constants'
|
||||||
import {
|
import {
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
useFilteredMods,
|
useFilteredMods,
|
||||||
@ -20,77 +19,156 @@ import '../../styles/filters.css'
|
|||||||
import '../../styles/pagination.css'
|
import '../../styles/pagination.css'
|
||||||
import '../../styles/search.css'
|
import '../../styles/search.css'
|
||||||
import '../../styles/styles.css'
|
import '../../styles/styles.css'
|
||||||
import { FilterOptions, ModDetails } from '../../types'
|
import { FilterOptions, ModDetails, SortBy } from '../../types'
|
||||||
import { DEFAULT_FILTER_OPTIONS, scrollIntoView } from 'utils'
|
import {
|
||||||
|
DEFAULT_FILTER_OPTIONS,
|
||||||
|
extractModData,
|
||||||
|
isModDataComplete
|
||||||
|
} from 'utils'
|
||||||
import { SearchInput } from 'components/SearchInput'
|
import { SearchInput } from 'components/SearchInput'
|
||||||
import { ModsPageLoaderResult } from './loader'
|
import { ModsPageLoaderResult } from './loader'
|
||||||
|
import { FetchModsOptions } from 'contexts/NDKContext'
|
||||||
|
import {
|
||||||
|
NDKFilter,
|
||||||
|
NDKKind,
|
||||||
|
NDKSubscription,
|
||||||
|
NDKSubscriptionCacheUsage
|
||||||
|
} from '@nostr-dev-kit/ndk'
|
||||||
|
|
||||||
export const ModsPage = () => {
|
export const ModsPage = () => {
|
||||||
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
const scrollTargetRef = useRef<HTMLDivElement>(null)
|
||||||
const { repostList, muteLists, nsfwList } =
|
const { repostList, muteLists, nsfwList } =
|
||||||
useLoaderData() as ModsPageLoaderResult
|
useLoaderData() as ModsPageLoaderResult
|
||||||
const { fetchMods } = useNDKContext()
|
const { ndk, fetchMods } = useNDKContext()
|
||||||
const [isFetching, setIsFetching] = useState(false)
|
const [isFetching, setIsFetching] = useState(false)
|
||||||
const [mods, setMods] = useState<ModDetails[]>([])
|
const [mods, setMods] = useState<ModDetails[]>([])
|
||||||
|
const [isLoadMoreVisible, setIsLoadMoreVisible] = useState(true)
|
||||||
const [filterOptions] = useLocalStorage<FilterOptions>(
|
const [filterOptions] = useLocalStorage<FilterOptions>(
|
||||||
'filter',
|
'filter',
|
||||||
DEFAULT_FILTER_OPTIONS
|
DEFAULT_FILTER_OPTIONS
|
||||||
)
|
)
|
||||||
|
|
||||||
const [page, setPage] = useState(1)
|
|
||||||
|
|
||||||
const userState = useAppSelector((state) => state.user)
|
const userState = useAppSelector((state) => state.user)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsFetching(true)
|
setIsFetching(true)
|
||||||
fetchMods({ source: filterOptions.source })
|
fetchMods({ source: filterOptions.source })
|
||||||
.then((res) => {
|
.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(() => {
|
.finally(() => {
|
||||||
setIsFetching(false)
|
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)
|
setIsFetching(true)
|
||||||
|
|
||||||
const until =
|
const fetchModsOptions: FetchModsOptions = {
|
||||||
mods.length > 0 ? mods[mods.length - 1].published_at - 1 : undefined
|
source: filterOptions.source
|
||||||
|
}
|
||||||
|
|
||||||
fetchMods({
|
if (lastMod) {
|
||||||
source: filterOptions.source,
|
if (filterOptions.sort === SortBy.Latest) {
|
||||||
until
|
fetchModsOptions.until = lastMod.edited_at - 1
|
||||||
})
|
} else if (filterOptions.sort === SortBy.Oldest) {
|
||||||
|
fetchModsOptions.since = lastMod.edited_at + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMods(fetchModsOptions)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setMods(res)
|
setMods((prevMods) => {
|
||||||
setPage((prev) => prev + 1)
|
const newMods = res
|
||||||
scrollIntoView(scrollTargetRef.current)
|
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(() => {
|
.finally(() => {
|
||||||
setIsFetching(false)
|
setIsFetching(false)
|
||||||
})
|
})
|
||||||
}, [filterOptions.source, mods, fetchMods])
|
}, [fetchMods, filterOptions, lastMod])
|
||||||
|
|
||||||
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])
|
|
||||||
|
|
||||||
const filteredModList = useFilteredMods(
|
const filteredModList = useFilteredMods(
|
||||||
mods,
|
mods,
|
||||||
@ -121,12 +199,17 @@ export const ModsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Pagination
|
{!isFetching && isLoadMoreVisible && filteredModList.length > 0 && (
|
||||||
page={page}
|
<div className='IBMSMListFeedLoadMore'>
|
||||||
disabledNext={mods.length < MOD_FILTER_LIMIT}
|
<button
|
||||||
handlePrev={handlePrev}
|
className='btn btnMain IBMSMListFeedLoadMoreBtn'
|
||||||
handleNext={handleNext}
|
type='button'
|
||||||
/>
|
onClick={handleLoadMore}
|
||||||
|
>
|
||||||
|
Load More
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user