import { Spinner, Button } from '@wordpress/components' import { useEffect, useState, useCallback, useRef, memo, } from '@wordpress/element' import { __, sprintf } from '@wordpress/i18n' import { useInView } from 'react-intersection-observer' import Masonry from 'react-masonry-css' import { Templates as TemplatesApi } from '@extendify/api/Templates' import { ImportTemplateBlock } from '@extendify/components/ImportTemplateBlock' import { useIsMounted } from '@extendify/hooks/helpers' import { useGlobalStore } from '@extendify/state/GlobalState' import { useTaxonomyStore } from '@extendify/state/Taxonomies' import { useTemplatesStore } from '@extendify/state/Templates' export const GridView = memo(function GridView() { const isMounted = useIsMounted() const templates = useTemplatesStore((state) => state.templates) const appendTemplates = useTemplatesStore((state) => state.appendTemplates) const [serverError, setServerError] = useState('') const [nothingFound, setNothingFound] = useState(false) const [loading, setLoading] = useState(false) const [loadMoreRef, inView] = useInView() const searchParamsRaw = useTemplatesStore((state) => state.searchParams) const currentType = useGlobalStore((state) => state.currentType) const resetTemplates = useTemplatesStore((state) => state.resetTemplates) const open = useGlobalStore((state) => state.open) const taxonomies = useTaxonomyStore((state) => state.taxonomies) const updateType = useTemplatesStore((state) => state.updateType) const updateTaxonomies = useTemplatesStore( (state) => state.updateTaxonomies, ) // Store the next page in case we have pagination const nextPage = useRef(useTemplatesStore.getState().nextPage) const searchParams = useRef(useTemplatesStore.getState().searchParams) const taxonomyType = searchParams.current.type === 'pattern' ? 'patternType' : 'layoutType' const currentTax = searchParams.current.taxonomies[taxonomyType] // Subscribing to the store will keep these values updates synchronously useEffect(() => { return useTemplatesStore.subscribe( (state) => state.nextPage, (n) => (nextPage.current = n), ) }, []) useEffect(() => { return useTemplatesStore.subscribe( (state) => state.searchParams, (s) => (searchParams.current = s), ) }, []) // Fetch the templates then add them to the current state const fetchTemplates = useCallback(() => { setServerError('') setNothingFound(false) const defaultError = __( 'Unknown error occured. Check browser console or contact support.', 'extendify', ) const args = { offset: nextPage.current } TemplatesApi.get(searchParams.current, args) .then((response) => { if (!isMounted.current) return if (response?.error?.length) { setServerError(response?.error) return } if (response?.records?.length <= 0) { setNothingFound(true) return } if ( searchParamsRaw === searchParams.current && response?.records?.length ) { useTemplatesStore.setState({ nextPage: response?.offset ?? '', }) appendTemplates(response.records) setLoading(false) } }) .catch((error) => { if (!isMounted.current) return console.error(error) setServerError(defaultError) }) }, [appendTemplates, isMounted, searchParamsRaw]) useEffect(() => { if (templates?.length === 0) { setLoading(true) return } }, [templates?.length, searchParamsRaw]) useEffect(() => { // This will check the URL for a pattern type and set that and remove it // TODO: possibly refactor this if we exapnd it to support layouts if (!open || !taxonomies?.patternType?.length) return const search = new URLSearchParams(window.location.search) if (!search.has('ext-patternType')) return const term = search.get('ext-patternType') // Delete it right away search.delete('ext-patternType') window.history.replaceState( null, null, window.location.pathname + '?' + search.toString(), ) // Search the slug in patternTypes const tax = taxonomies.patternType.find((t) => t.slug === term) if (!tax) return updateTaxonomies({ patternType: tax }) updateType('pattern') }, [open, taxonomies, updateType, updateTaxonomies]) // This is the main driver for loading templates // This loads the initial batch of templates. But if we don't yet have taxonomies. // There's also an option to skip loading on first mount useEffect(() => { if (!Object.keys(searchParams.current?.taxonomies)?.length) { return } if (useTemplatesStore.getState().skipNextFetch) { // This is useful if the templates are fetched already and // the library moves to/from another state that re-renders // The point is to keep the logic close to the list. That may change someday useTemplatesStore.setState({ skipNextFetch: false, }) return } fetchTemplates() return () => resetTemplates() }, [fetchTemplates, searchParams, resetTemplates]) // Fetches when the load more is in view useEffect(() => { nextPage.current && inView && fetchTemplates() }, [inView, fetchTemplates, templates]) if (serverError.length) { return (
{serverError}