diff --git a/src/admin/ClearCacheButton.tsx b/src/admin/ClearCacheButton.tsx index 0e14ba3a..7f981ff9 100644 --- a/src/admin/ClearCacheButton.tsx +++ b/src/admin/ClearCacheButton.tsx @@ -2,19 +2,13 @@ import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; import { syncCacheAction } from '@/photo/actions'; -import { useMoreComponentsState } from '@/state/MoreComponentsState'; import { BiTrash } from 'react-icons/bi'; export default function ClearCacheButton() { - const { - clearMoreComponentsState, - } = useMoreComponentsState(); - return (
} - onFormSubmit={clearMoreComponentsState} > Clear Cache diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4b4d4bd4..2a29f043 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -7,7 +7,6 @@ import AppStateProvider from '@/state/AppStateProvider'; import ToasterWithThemes from '@/toast/ToasterWithThemes'; import PhotoEscapeHandler from '@/photo/PhotoEscapeHandler'; import { Metadata } from 'next/types'; -import MoreComponentsProvider from '@/state/MoreComponentsProvider'; import { ThemeProvider } from 'next-themes'; import Nav from '@/site/Nav'; import Footer from '@/site/Footer'; @@ -74,24 +73,22 @@ export default function RootLayout({ - - -
+
+
- - - + {children} + +
+
+ +
diff --git a/src/components/MoreComponents.tsx b/src/components/MoreComponents.tsx deleted file mode 100644 index 22dd71db..00000000 --- a/src/components/MoreComponents.tsx +++ /dev/null @@ -1,252 +0,0 @@ -'use client'; - -import { useCallback, useEffect, useRef } from 'react'; -import Spinner from './Spinner'; -import SiteGrid from './SiteGrid'; -import { - MoreComponentsKey, - MoreComponentsStateForKeyArgument, - useMoreComponentsState, -} from '@/state/MoreComponentsState'; - -const MAX_ATTEMPTS_PER_REQUEST = 5; -const MAX_TOTAL_REQUESTS = 100; -const RETRY_DELAY_IN_SECONDS = 1; - -export default function MoreComponents({ - stateKey, - initialOffset, - itemsPerRequest, - getNextComponent, - label = 'Load more', - triggerOnView = true, - prefetch = true, - itemsClass, - wrapMoreButtonInSiteGrid, - debug, -}: { - stateKey: MoreComponentsKey - initialOffset: number - itemsPerRequest: number - getNextComponent: (offset: number, limit: number) => Promise<{ - nextComponent?: JSX.Element, - isFinished?: boolean, - didFail?: boolean, - }> - label?: string - triggerOnView?: boolean - prefetch?: boolean - itemsClass?: string - wrapMoreButtonInSiteGrid?: boolean - debug?: boolean -}) { - const { state, setStateForKey } = useMoreComponentsState(); - - const setState = useCallback( - (stateForKey: MoreComponentsStateForKeyArgument) => - setStateForKey(stateKey, stateForKey), - [setStateForKey, stateKey]); - - useEffect(() => { - setState({ hasMounted: true }); - }, [setState]); - - const { - hasMounted, - isLoading, - indexInView, - finalIndex, - didReachMaximumRequests, - components, - } = state[stateKey]; - - // When prefetching, always stay one request ahead of what's visible - const furthestIndexToLoad = Math.min( - prefetch ? (indexInView ?? 0) + 1 : (indexInView ?? 0), - finalIndex ?? Infinity, - ); - - const indexToLoad = Math.min( - components.length, - furthestIndexToLoad, - ); - - const attemptsPerRequest = useRef(0); - const totalRequests = useRef(0); - - const hasFinalIndexBeenReached = - finalIndex !== undefined && - finalIndex <= components.length - 1; - - const areAllComponentsVisible = - (indexInView ?? 0) >= components.length - 1; - - const showMoreButton = - isLoading || - !hasFinalIndexBeenReached || - !areAllComponentsVisible; - - const currentTimeout = useRef(); - - const attempt = useCallback(() => { - // Consider creating temp, anonymous function - // for error handling - const attemptRetry = () => { - if (currentTimeout.current) { - clearTimeout(currentTimeout.current); - } - currentTimeout.current = - setTimeout(attempt, RETRY_DELAY_IN_SECONDS * 1000); - }; - if (attemptsPerRequest.current < MAX_ATTEMPTS_PER_REQUEST) { - if (totalRequests.current < MAX_TOTAL_REQUESTS) { - attemptsPerRequest.current += 1; - totalRequests.current += 1; - setState({ isLoading: true }); - if (debug) { - // eslint-disable-next-line max-len - console.log(`GETTING INDEX: #${indexToLoad}, ATTEMPT: #${attemptsPerRequest.current}`); - } - getNextComponent( - initialOffset + indexToLoad * itemsPerRequest, - itemsPerRequest, - ) - .then(({ nextComponent, isFinished, didFail }) => { - if (!didFail) { - attemptsPerRequest.current = 0; - setState(state => { - const updatedComponents = [...state.components]; - if (nextComponent) { - updatedComponents[indexToLoad] = nextComponent; - } - return { - ...state, - ...nextComponent && { components: updatedComponents}, - latestIndexLoaded: indexToLoad, - isLoading: false, - didReachMaximumRequests: false, - ...isFinished && { - // Special case when finished on first request - finalIndex: indexToLoad === 0 ? -1 : indexToLoad, - }, - }; - }); - } else { - attemptRetry(); - } - }) - .catch(attemptRetry); - } else { - console.log( - `Max total attempts reached (${MAX_TOTAL_REQUESTS})` - ); - setState({ - isLoading: false, - didReachMaximumRequests: true, - }); - } - } else { - console.log( - `Max attempts per request reached ${MAX_ATTEMPTS_PER_REQUEST}` - ); - setState({ - isLoading: false, - didReachMaximumRequests: true, - }); - } - }, [ - setState, - getNextComponent, - initialOffset, - indexToLoad, - itemsPerRequest, - debug, - ]); - - useEffect(() => { - if ( - !isLoading && - indexToLoad >= components.length - ) { - attempt(); - } - }, [isLoading, indexToLoad, attempt, components.length]); - - const buttonRef = useRef(null); - - const resetRequestsAndRetry = useCallback(() => { - attemptsPerRequest.current = 0; - totalRequests.current = 0; - setState({ didReachMaximumRequests: false }); - attempt(); - }, [attempt, setState]); - - const advance = useCallback(() => { - if (indexInView === undefined) { - setState({ indexInView: 0 }); - } else if ( - indexInView <= components.length - 1 && ( - finalIndex === undefined || - indexInView < finalIndex - ) - ) { - setState({ indexInView: indexInView + 1}); - } - }, [components.length, finalIndex, indexInView, setState]); - - useEffect(() => { - // Only add observer if button is rendered - if (buttonRef.current) { - const observer = new IntersectionObserver(e => { - if (triggerOnView && e[0].isIntersecting) { - advance(); - } - }, { - root: null, - threshold: 0, - }); - observer.observe(buttonRef.current); - return () => observer.disconnect(); - } - }, [triggerOnView, advance]); - - const renderMoreButton = () => - ; - - if (debug) { - console.log({ - indexInView, - componentsLength: components.length, - finalIndex, - hasFinalIndexBeenReached, - areAllComponentsVisible, - isLoading, - }); - } - - return <> -
-
- {components.slice(0, (indexInView ?? 0) + 1)} -
- {showMoreButton && ( - wrapMoreButtonInSiteGrid - ? - : renderMoreButton() - )} -
- ; -} diff --git a/src/photo/MorePhotosGrid.tsx b/src/photo/MorePhotosGrid.tsx deleted file mode 100644 index ec39f293..00000000 --- a/src/photo/MorePhotosGrid.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import MoreComponents from '@/components/MoreComponents'; -import { getPhotosCached } from '@/photo/cache'; -import PhotoGrid from './PhotoGrid'; -import { useCallback } from 'react'; - -export function MorePhotosGrid({ - initialOffset, - itemsPerRequest, - totalPhotosCount, -}: { - initialOffset: number - itemsPerRequest: number - totalPhotosCount: number -}) { - const getNextComponent = useCallback(async ( - offset: number, - limit: number, - ) => { - 'use server'; - if ( - process.env.NODE_ENV === 'development' && - Math.random() < 0.5 - ) { - return { didFail: true }; - } - const photos = await getPhotosCached({ limit: offset + limit }) - .catch(() => undefined); - if (!photos) { - return { didFail: true }; - } else { - const nextPhotos = photos.slice(offset); - return { - ...nextPhotos.length > 0 && { - nextComponent: , - }, - isFinished: offset + limit >= totalPhotosCount, - }; - } - }, [totalPhotosCount]); - - return ( - initialOffset <= totalPhotosCount - ? - : null - ); -} diff --git a/src/photo/MorePhotosRoot.tsx b/src/photo/MorePhotosRoot.tsx deleted file mode 100644 index a3e98af8..00000000 --- a/src/photo/MorePhotosRoot.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import MoreComponents from '@/components/MoreComponents'; -import PhotosLarge from './PhotosLarge'; -import { getPhotosCached } from '@/photo/cache'; -import { useCallback } from 'react'; - -export function MorePhotosRoot({ - initialOffset, - itemsPerRequest, - totalPhotosCount, -}: { - initialOffset: number - itemsPerRequest: number - totalPhotosCount: number -}) { - const getNextComponent = useCallback(async ( - offset: number, - limit: number, - ) => { - 'use server'; - if ( - process.env.NODE_ENV === 'development' && - Math.random() < 0.1 - ) { - return { didFail: true }; - } - const photos = await getPhotosCached({ limit: offset + limit }) - .catch(() => undefined); - if (!photos) { - return { didFail: true }; - } else { - const nextPhotos = photos.slice(offset); - return { - nextComponent: , - isFinished: offset + limit >= totalPhotosCount, - }; - } - }, [totalPhotosCount]); - - return ( - initialOffset <= totalPhotosCount - ? - :null - ); -} diff --git a/src/state/MoreComponentsProvider.tsx b/src/state/MoreComponentsProvider.tsx deleted file mode 100644 index c0380158..00000000 --- a/src/state/MoreComponentsProvider.tsx +++ /dev/null @@ -1,47 +0,0 @@ -'use client'; - -import { ReactNode, useCallback, useState } from 'react'; -import { - MoreComponentsContext, - MoreComponentsKey, - MoreComponentsState, - MORE_COMPONENTS_INITIAL_STATE, - MoreComponentsStateForKeyArgument, -} from './MoreComponentsState'; - -export default function MoreComponentsProvider({ - children, -}: { - children: ReactNode -}) { - const [state, setState] = - useState(MORE_COMPONENTS_INITIAL_STATE); - - const setStateForKey = useCallback(( - key: MoreComponentsKey, - state: MoreComponentsStateForKeyArgument - ) => { - setState(existingState => ({ - ...existingState, - ...typeof state === 'function' - ? { [key]: state(existingState[key]) } - : { [key]: { ...existingState[key], ...state } }, - })); - }, []); - - const clearMoreComponentsState = useCallback(() => { - setState(MORE_COMPONENTS_INITIAL_STATE); - }, []); - - return ( - - {children} - - ); -} diff --git a/src/state/MoreComponentsState.ts b/src/state/MoreComponentsState.ts deleted file mode 100644 index e31f1023..00000000 --- a/src/state/MoreComponentsState.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { createContext, useContext } from 'react'; - -export type MoreComponentsKey = - 'PhotosRoot' | - 'PhotosGrid'; - -export interface MoreComponentsStateForKey { - hasMounted: boolean - isLoading: boolean - indexInView?: number - finalIndex?: number - didReachMaximumRequests: boolean - components: JSX.Element[] -} - -export const createInitialStateForKey = - (): MoreComponentsStateForKey => ({ - hasMounted: false, - isLoading: false, - didReachMaximumRequests: false, - components: [], - }); - -export type MoreComponentsState = Record< - MoreComponentsKey, - MoreComponentsStateForKey ->; - -export type MoreComponentsStateForKeyArgument = - Partial | - ((existingValue: MoreComponentsStateForKey) => MoreComponentsStateForKey); - -export interface MoreComponentsContext { - state: MoreComponentsState - setStateForKey: ( - key: MoreComponentsKey, - state: MoreComponentsStateForKeyArgument, - ) => void - clearMoreComponentsState: () => void -} - -export const MORE_COMPONENTS_INITIAL_STATE: MoreComponentsState = { - PhotosRoot: createInitialStateForKey(), - PhotosGrid: createInitialStateForKey(), -}; - -export const MoreComponentsContext = - createContext({ - state: MORE_COMPONENTS_INITIAL_STATE, - setStateForKey: () => {}, - clearMoreComponentsState: () => {}, - }); - -export const useMoreComponentsState = () => - useContext(MoreComponentsContext); diff --git a/src/state/useSwrClear.ts b/src/state/useSwrClear.ts deleted file mode 100644 index 8ad422bc..00000000 --- a/src/state/useSwrClear.ts +++ /dev/null @@ -1,13 +0,0 @@ -'use client'; - -import { useCallback } from 'react'; -import { useSWRConfig } from 'swr'; - -export default function useSwrClear() { - const { mutate } = useSWRConfig(); - return useCallback(() => mutate( - _key => true, - undefined, - { revalidate: false }, - ), [mutate]); -}