diff --git a/packages/web/components/nav-containers/HomeContainer.tsx b/packages/web/components/nav-containers/HomeContainer.tsx index b6f80a65f..e4dabc403 100644 --- a/packages/web/components/nav-containers/HomeContainer.tsx +++ b/packages/web/components/nav-containers/HomeContainer.tsx @@ -211,6 +211,7 @@ export function HomeContainer(): JSX.Element { const shouldFallback = homeData.error || (!homeData.isValidating && !hasTopPicks(homeData)) const searchData = useGetLibraryItems( + 'home', undefined, { limit: 10, diff --git a/packages/web/components/templates/discoverFeed/DiscoverContainer.tsx b/packages/web/components/templates/discoverFeed/DiscoverContainer.tsx index 76174e877..9ece5196b 100644 --- a/packages/web/components/templates/discoverFeed/DiscoverContainer.tsx +++ b/packages/web/components/templates/discoverFeed/DiscoverContainer.tsx @@ -89,7 +89,7 @@ export function DiscoverContainer(): JSX.Element { } setPage(page + 1) }, [page, isLoading]) - useFetchMore(handleFetchMore) + // useFetchMore(handleFetchMore) const handleSaveDiscover = async ( discoverArticleId: string, diff --git a/packages/web/components/templates/library/LibraryContainer.tsx b/packages/web/components/templates/library/LibraryContainer.tsx index 589dce6a3..f344e609f 100644 --- a/packages/web/components/templates/library/LibraryContainer.tsx +++ b/packages/web/components/templates/library/LibraryContainer.tsx @@ -54,8 +54,8 @@ import { theme } from '../../tokens/stitches.config' import { emptyTrashMutation } from '../../../lib/networking/mutations/emptyTrashMutation' import { State } from '../../../lib/networking/fragments/articleFragment' import { useHandleAddUrl } from '../../../lib/hooks/useHandleAddUrl' -import { QueryClient, useQueryClient } from '@tanstack/react-query' import { useGetViewer } from '../../../lib/networking/viewer/useGetViewer' +import { Spinner } from '@phosphor-icons/react/dist/ssr' export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT' @@ -117,11 +117,14 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element { const { data: itemsPages, isLoading, + isFetchingNextPage, isFetching, fetchNextPage, + fetchPreviousPage, hasNextPage, + hasPreviousPage, error: fetchItemsError, - } = useGetLibraryItems(props.folder, queryInputs) + } = useGetLibraryItems(props.folder ?? 'home', props.folder, queryInputs) useEffect(() => { if (queryValue.startsWith('#')) { @@ -157,6 +160,7 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element { }, [router.asPath]) const libraryItems = useMemo(() => { + console.log('library items: ', itemsPages) const items = itemsPages?.pages .flatMap((ad: LibraryItems) => { @@ -184,16 +188,16 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element { .map((li) => li.node.id) }, [libraryItems]) - const refreshProcessingItems = useRefreshProcessingItems() + // const refreshProcessingItems = useRefreshProcessingItems() - useEffect(() => { - if (processingItems.length) { - refreshProcessingItems.mutateAsync({ - attempt: 0, - itemIds: processingItems, - }) - } - }, [processingItems]) + // useEffect(() => { + // if (processingItems.length) { + // refreshProcessingItems.mutateAsync({ + // attempt: 0, + // itemIds: processingItems, + // }) + // } + // }, [processingItems]) const focusFirstItem = useCallback(() => { if (libraryItems.length < 1) { @@ -295,6 +299,7 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element { }, [libraryItems, activeCardId]) useEffect(() => { + console.log('active card id: ', activeCardId) if (activeCardId && !alreadyScrolled.current) { scrollToActiveCard(activeCardId) alreadyScrolled.current = true @@ -789,6 +794,33 @@ export function LibraryContainer(props: LibraryContainerProps): JSX.Element { [itemsPages, multiSelectMode, checkedItems] ) + // return ( + // Loading...} + // endMessage={ + //

+ // Yay! You have seen it all + //

+ // } + // > + // {libraryItems.map((item) => { + // return ( + // { + // router.push(`/${viewerData?.profile.username}/${item.node.slug}`) + // }} + // > + // {item.cursor}: {item.node.title} + // + // ) + // })} + //
+ // ) + return ( {props.hasMore ? ( - + props.isValidating ? ( + + ) : ( + + ) ) : ( )} diff --git a/packages/web/lib/hooks/useFetchMoreScroll.tsx b/packages/web/lib/hooks/useFetchMoreScroll.tsx index eb7a1759f..93bf9a557 100644 --- a/packages/web/lib/hooks/useFetchMoreScroll.tsx +++ b/packages/web/lib/hooks/useFetchMoreScroll.tsx @@ -1,28 +1,24 @@ import { useEffect, useRef, useState } from 'react' -export const useFetchMore = (callback: () => void, delay = 500): void => { +export const useFetchMore = (fetchNextPage: () => void, delay = 500): void => { const [first, setFirst] = useState(true) + const [lastScrollTop, setLastScrollTop] = useState(0) const throttleTimeout = useRef(undefined) useEffect(() => { - if (typeof window === 'undefined') { - return - } - const callbackInternal = (): void => { const { scrollTop, scrollHeight, clientHeight } = window.document.documentElement + const direction = scrollTop > lastScrollTop ? 'down' : 'up' + setLastScrollTop(scrollTop) - if (scrollTop + clientHeight >= scrollHeight - scrollHeight / 3) { - console.log( - 'calling fetchMore: scrollTop + clientHeight >= scrollHeight - scrollHeight / 3', - scrollTop, - clientHeight, - scrollHeight, - scrollHeight / 3 - ) - callback() + if ( + direction == 'down' && + scrollTop + clientHeight >= scrollHeight - scrollHeight / 3 + ) { + fetchNextPage() } + throttleTimeout.current = undefined } @@ -42,5 +38,5 @@ export const useFetchMore = (callback: () => void, delay = 500): void => { return () => { window.removeEventListener('scroll', handleScroll) } - }, [callback, delay, first, setFirst]) + }, [fetchNextPage, delay, first, setFirst]) } diff --git a/packages/web/lib/networking/library_items/useLibraryItems.tsx b/packages/web/lib/networking/library_items/useLibraryItems.tsx index 50d1ba518..d9fa8ccbc 100644 --- a/packages/web/lib/networking/library_items/useLibraryItems.tsx +++ b/packages/web/lib/networking/library_items/useLibraryItems.tsx @@ -23,6 +23,7 @@ import { GQL_SET_LINK_ARCHIVED, GQL_UPDATE_LIBRARY_ITEM, } from './gql' +import { useState } from 'react' function gqlFetcher( query: string, @@ -173,11 +174,8 @@ export const insertItemInCache = ( const keys = queryClient .getQueryCache() .findAll({ queryKey: ['libraryItems'] }) - console.log('keys: ', keys) - keys.forEach((query) => { queryClient.setQueryData(query.queryKey, (data: any) => { - console.log('data, data.pages', data) if (!data) return data if (data.pages.length > 0) { const firstPage = data.pages[0] as LibraryItems @@ -202,43 +200,139 @@ export const insertItemInCache = ( }, ] data.pages[0] = firstPage - console.log('data: ', data) return data } }) }) } +// const useOptimizedPageFetcher = ( +// section: string, +// folder: string | undefined, +// { limit, searchQuery, includeCount }: LibraryItemsQueryInput, +// enabled = true +// ) => { +// const [pages, setPages] = useState([]) +// const queryClient = useQueryClient() +// const fullQuery = folder +// ? (`in:${folder} use:folders ` + (searchQuery ?? '')).trim() +// : searchQuery ?? '' +// } + +interface CachedPagesData { + pageParams: string[] + pages: LibraryItems[] +} + export function useGetLibraryItems( + section: string, folder: string | undefined, { limit, searchQuery, includeCount }: LibraryItemsQueryInput, enabled = true ) { + const queryClient = useQueryClient() + + const INITIAL_INDEX = '0' const fullQuery = folder ? (`in:${folder} use:folders ` + (searchQuery ?? '')).trim() : searchQuery ?? '' + const queryKey = ['libraryItems', section, fullQuery] return useInfiniteQuery({ - queryKey: ['libraryItems', fullQuery], - queryFn: async ({ pageParam }) => { + // If no folder is specified cache this as `home` + queryKey, + queryFn: async ({ queryKey, pageParam, meta }) => { + console.log('pageParam and limit', Number(pageParam), limit) + const cached = queryClient.getQueryData(queryKey) as CachedPagesData + if (pageParam !== INITIAL_INDEX) { + // check in the query cache, if there is an item for this page + // in the query page, check if pageIndex - 1 was unchanged since + // the last query, this will determine if we should refetch this + // page and subsequent pages. + if (cached) { + const idx = cached.pageParams.indexOf(pageParam) + + // First check if the previous page had detected a modification + // if it had we keep fetching until we find a + if ( + idx > 0 && + idx < cached.pages.length && + cached.pages[idx - 1].pageInfo.wasUnchanged + ) { + const cachedResult = cached.pages[idx] + console.log('found cached page result: ', cachedResult) + return { + edges: cachedResult.edges, + pageInfo: { + ...cachedResult.pageInfo, + wasUnchanged: true, + }, + } + } + } + } const response = (await gqlFetcher(gqlSearchQuery(includeCount), { after: pageParam, first: limit, query: fullQuery, includeContent: false, })) as LibraryItemsData - return response.search + let wasUnchanged = false + if (cached && cached.pageParams.indexOf(pageParam) > -1) { + const idx = cached.pageParams.indexOf(pageParam) + // // if there is a cache, check to see if the page is already in it + // // and mark whether or not the page has changed + try { + const cachedIds = cached.pages[idx].edges.map((m) => m.node.id) + const resultIds = response.search.edges.map((m) => m.node.id) + const compareFunc = (a: string[], b: string[]) => + a.length === b.length && + a.every((element, index) => element === b[index]) + wasUnchanged = compareFunc(cachedIds, resultIds) + console.log('previous unchanged', wasUnchanged, cachedIds, resultIds) + } catch (err) { + console.log('error: ', err) + } + } + return { + edges: response.search.edges, + pageInfo: { + ...response.search.pageInfo, + wasUnchanged, + lastUpdated: new Date(), + }, + } }, enabled, - initialPageParam: '0', - refetchOnMount: false, - refetchOnWindowFocus: false, - staleTime: 10 * 60 * 1000, - getNextPageParam: (lastPage: LibraryItems) => { + initialPageParam: INITIAL_INDEX, + getNextPageParam: (lastPage: LibraryItems, pages) => { return lastPage.pageInfo.hasNextPage ? lastPage?.pageInfo?.endCursor : undefined }, + select: (data) => { + const now = new Date() + + // Filter pages based on the lastUpdated condition + const filteredPages = data.pages.slice(0, 5).concat( + data.pages.slice(5).filter((page, index) => { + if (page.pageInfo?.lastUpdated) { + const lastUpdatedDate = new Date(page.pageInfo.lastUpdated) + const diffMinutes = + (now.getTime() - lastUpdatedDate.getTime()) / (1000 * 60) + console.log(`page: ${index} age: ${diffMinutes}`) + return diffMinutes <= 10 + } + return true + }) + ) + console.log('setting filteredPages: ', filteredPages) + + return { + ...data, + pages: filteredPages, + } + }, }) } @@ -531,7 +625,9 @@ export function useRefreshProcessingItems() { attempt: number itemIds: string[] }) => { - const fullQuery = `in:all includes:${variables.itemIds.join(',')}` + const fullQuery = `in:all includes:${variables.itemIds + .slice(0, 5) + .join(',')}` const result = (await gqlFetcher(gqlSearchQuery(), { first: 10, query: fullQuery, @@ -1029,6 +1125,10 @@ export type PageInfo = { startCursor: string endCursor: string totalCount: number + + // used internally for some cache handling + lastUpdated?: Date + wasUnchanged?: boolean } type SetLinkArchivedInput = { diff --git a/packages/web/lib/toastHelpers.tsx b/packages/web/lib/toastHelpers.tsx index b73806f84..5343acfdd 100644 --- a/packages/web/lib/toastHelpers.tsx +++ b/packages/web/lib/toastHelpers.tsx @@ -114,8 +114,6 @@ const showToastWithAction = ( action: () => Promise, options?: ToastOptions ) => { - console.trace('show success: ', message) - return toast( ({ id }) => ( diff --git a/packages/web/pages/[username]/[slug]/index.tsx b/packages/web/pages/[username]/[slug]/index.tsx index 7fcd54189..087c7ed06 100644 --- a/packages/web/pages/[username]/[slug]/index.tsx +++ b/packages/web/pages/[username]/[slug]/index.tsx @@ -305,7 +305,7 @@ export default function Reader(): JSX.Element { perform: () => { const navReturn = window.localStorage.getItem('nav-return') if (navReturn) { - router.push(navReturn) + router.push(navReturn, navReturn, { scroll: false }) return } const query = window.sessionStorage.getItem('q') diff --git a/packages/web/pages/_app.tsx b/packages/web/pages/_app.tsx index 234192d3b..2a0b38aa9 100644 --- a/packages/web/pages/_app.tsx +++ b/packages/web/pages/_app.tsx @@ -33,7 +33,7 @@ import React from 'react' const queryClient = new QueryClient({ defaultOptions: { queries: { - gcTime: 1000 * 60 * 60 * 48, // 48 hours + gcTime: 1000 * 60 * 60 * 4, // 4hrs }, }, }) diff --git a/packages/web/pages/l/[section].tsx b/packages/web/pages/l/[section].tsx index 522563e89..cb75330ec 100644 --- a/packages/web/pages/l/[section].tsx +++ b/packages/web/pages/l/[section].tsx @@ -45,6 +45,7 @@ export default function Home(): JSX.Element { // return return ( { return ( @@ -59,6 +60,7 @@ export default function Home(): JSX.Element { case 'library': return ( { return ( @@ -73,6 +75,7 @@ export default function Home(): JSX.Element { case 'subscriptions': return ( { return ( @@ -87,6 +90,7 @@ export default function Home(): JSX.Element { case 'search': return ( { console.log('item: ', item) @@ -98,6 +102,7 @@ export default function Home(): JSX.Element { case 'archive': return ( { return item.state == 'ARCHIVED' @@ -108,6 +113,7 @@ export default function Home(): JSX.Element { case 'trash': return ( { return item.state == 'DELETED' diff --git a/packages/web/pages/settings/account.tsx b/packages/web/pages/settings/account.tsx index a86ad183e..40d215c91 100644 --- a/packages/web/pages/settings/account.tsx +++ b/packages/web/pages/settings/account.tsx @@ -92,7 +92,7 @@ export default function Account(): JSX.Element { isUsernameValidationLoading, ]) - const { data: itemsPages, isLoading } = useGetLibraryItems('all', { + const { data: itemsPages, isLoading } = useGetLibraryItems('search', 'all', { limit: 0, searchQuery: '', sortDescending: false, diff --git a/packages/web/pages/tools/bulk.tsx b/packages/web/pages/tools/bulk.tsx index b5083f8a4..9e82a389a 100644 --- a/packages/web/pages/tools/bulk.tsx +++ b/packages/web/pages/tools/bulk.tsx @@ -33,12 +33,16 @@ export default function BulkPerformer(): JSX.Element { const [runningState, setRunningState] = useState('none') const bulkAction = useBulkActions() - const { data: itemsPages, isLoading } = useGetLibraryItems(undefined, { - searchQuery: query, - limit: 1, - sortDescending: false, - includeCount: true, - }) + const { data: itemsPages, isLoading } = useGetLibraryItems( + 'search', + undefined, + { + searchQuery: query, + limit: 1, + sortDescending: false, + includeCount: true, + } + ) useEffect(() => { setExpectedCount(itemsPages?.pages.find(() => true)?.pageInfo.totalCount) diff --git a/yarn.lock b/yarn.lock index 06fab41f2..d6c948ec1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29428,7 +29428,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -29454,15 +29454,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.2, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -29617,7 +29608,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -29652,13 +29643,6 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -32358,7 +32342,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -32384,15 +32368,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"