/home/arranoyd/empl/wp-content/plugins/redux-framework/extendify-sdk/src/pages/GridView.js
import Masonry from 'react-masonry-css'
import {
useEffect,
useState,
useCallback,
useRef,
memo,
} from '@wordpress/element'
import { Spinner, Button } from '@wordpress/components'
import { __, sprintf } from '@wordpress/i18n'
import { useTemplatesStore } from '../state/Templates'
import { Templates as TemplatesApi } from '../api/Templates'
import { useInView } from 'react-intersection-observer'
import { useIsMounted } from '../hooks/helpers'
import { ImportTemplateBlock } from '../components/ImportTemplateBlock'
import { useGlobalStore } from '../state/GlobalState'
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)
// 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(
(n) => (nextPage.current = n),
(state) => state.nextPage,
)
}, [])
useEffect(() => {
return useTemplatesStore.subscribe(
(s) => (searchParams.current = s),
(state) => state.searchParams,
)
}, [])
// 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])
// 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()
}, [fetchTemplates, searchParams])
// Fetches when the load more is in view
useEffect(() => {
nextPage.current && inView && fetchTemplates()
}, [inView, fetchTemplates, templates])
if (serverError.length) {
return (
<div className="text-left">
<h2 className="text-left">{__('Server error', 'extendify')}</h2>
<code
className="block max-w-xl p-4 mb-4"
style={{ minHeight: '10rem' }}>
{serverError}
</code>
<Button
isTertiary
onClick={() => resetTemplates() && fetchTemplates()}>
{__('Press here to reload')}
</Button>
</div>
)
}
if (nothingFound) {
return (
<div className="flex h-full items-center justify-center w-full -mt-2 sm:mt-0">
<h2 className="text-sm text-extendify-gray font-normal">
{sprintf(
searchParams.current.type === 'template'
? __(
'We couldn\'t find any layouts in the "%s" category.',
'extendify',
)
: __(
'We couldn\'t find any patterns in the "%s" category.',
'extendify',
),
currentTax?.title ?? currentTax.slug,
)}
</h2>
</div>
)
}
return (
<>
{loading && (
<div className="flex h-full items-center justify-center w-full -mt-2 sm:mt-0">
<Spinner />
</div>
)}
<Grid type={currentType} templates={templates}>
{templates.map((template) => {
return (
<ImportTemplateBlock
maxHeight={
currentType === 'template' ? 520 : 'none'
}
key={template.id}
template={template}
/>
)
})}
</Grid>
{nextPage.current && (
<>
<div className="my-20">
<Spinner />
</div>
{/* This is a large div that, when in view, will trigger more patterns to load */}
<div
className="-translate-y-full flex flex-col items-end justify-end relative transform"
ref={loadMoreRef}
style={{
zIndex: -1,
marginBottom: '-100%',
height:
currentType === 'template' ? '150vh' : '75vh',
}}
/>
</>
)}
</>
)
})
const Grid = ({ type, children }) => {
const sharedClasses = 'relative min-h-screen z-10 pb-40 pt-0.5'
switch (type) {
case 'template':
return (
<div
className={`grid lg:grid-cols-2 gap-6 md:gap-8 ${sharedClasses}`}>
{children}
</div>
)
}
const breakpointColumnsObj = {
default: 3,
1600: 2,
860: 1,
599: 2,
400: 1,
}
return (
<Masonry
breakpointCols={breakpointColumnsObj}
className={`flex -ml-6 md:-ml-8 w-auto px-0.5 ${sharedClasses}`}
columnClassName="pl-6 md:pl-8 bg-clip-padding space-y-6 md:space-y-8">
{children}
</Masonry>
)
}