PluginProbe
Extendify / 0.3.0
Extendify v0.3.0
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / pages / GridView.js

GridView.js in Extendify 0.3.0, at src/pages/GridView.js

207 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import Masonry from 'react-masonry-css'
2 import {
3 useEffect,
4 useState,
5 useCallback,
6 useRef,
7 memo,
8 } from '@wordpress/element'
9 import { Spinner, Button } from '@wordpress/components'
10 import { __, sprintf } from '@wordpress/i18n'
11 import { useTemplatesStore } from '../state/Templates'
12 import { Templates as TemplatesApi } from '../api/Templates'
13 import { useInView } from 'react-intersection-observer'
14 import { useIsMounted } from '../hooks/helpers'
15 import { ImportTemplateBlock } from '../components/ImportTemplateBlock'
16
17 export const GridView = memo(() => {
18 const isMounted = useIsMounted()
19 const templates = useTemplatesStore((state) => state.templates)
20 const appendTemplates = useTemplatesStore((state) => state.appendTemplates)
21 const [serverError, setServerError] = useState('')
22 const [nothingFound, setNothingFound] = useState(false)
23 const [loading, setLoading] = useState(false)
24 const [loadMoreRef, inView] = useInView()
25 const searchParamsRaw = useTemplatesStore((state) => state.searchParams)
26 const resetTemplates = useTemplatesStore((state) => state.resetTemplates)
27
28 // Store the next page in case we have pagination
29 const nextPage = useRef(useTemplatesStore.getState().nextPage)
30 const searchParams = useRef(useTemplatesStore.getState().searchParams)
31 const taxonomyType =
32 searchParams.current.type === 'pattern' ? 'patternType' : 'layoutType'
33 const currentTax = searchParams.current.taxonomies[taxonomyType]
34
35 // Subscribing to the store will keep these values updates synchronously
36 useEffect(() => {
37 return useTemplatesStore.subscribe(
38 (n) => (nextPage.current = n),
39 (state) => state.nextPage,
40 )
41 }, [])
42 useEffect(() => {
43 return useTemplatesStore.subscribe(
44 (s) => (searchParams.current = s),
45 (state) => state.searchParams,
46 )
47 }, [])
48
49 // Fetch the templates then add them to the current state
50 const fetchTemplates = useCallback(() => {
51 setServerError('')
52 setNothingFound(false)
53 const defaultError = __(
54 'Unknown error occured. Check browser console or contact support.',
55 'extendify',
56 )
57 const args = { offset: nextPage.current }
58 TemplatesApi.get(searchParams.current, args)
59 .then((response) => {
60 if (!isMounted.current) return
61 if (response?.error?.length) {
62 setServerError(response?.error)
63 return
64 }
65 if (response?.records?.length <= 0) {
66 setNothingFound(true)
67 return
68 }
69 if (
70 searchParamsRaw === searchParams.current &&
71 response?.records.length
72 ) {
73 useTemplatesStore.setState({
74 nextPage: response?.offset ?? '',
75 })
76 appendTemplates(response.records)
77 setLoading(false)
78 }
79 })
80 .catch((error) => {
81 if (!isMounted.current) return
82 console.error(error)
83 setServerError(defaultError)
84 })
85 }, [appendTemplates, isMounted, searchParamsRaw])
86
87 useEffect(() => {
88 if (templates.length === 0) {
89 setLoading(true)
90 return
91 }
92 }, [templates.length, searchParamsRaw])
93
94 // This is the main driver for loading templates
95 // This loads the initial batch of templates. But if we don't yet have taxonomies.
96 // There's also an option to skip loading on first mount
97 useEffect(() => {
98 if (!Object.keys(searchParams.current.taxonomies).length) {
99 return
100 }
101
102 if (useTemplatesStore.getState().skipNextFetch) {
103 // This is useful if the templates are fetched already and
104 // the library moves to/from another state that re-renders
105 // The point is to keep the logic close to the list. That may change someday
106 useTemplatesStore.setState({
107 skipNextFetch: false,
108 })
109 return
110 }
111 fetchTemplates()
112 }, [fetchTemplates, searchParams])
113
114 // Fetches when the load more is in view
115 useEffect(() => {
116 nextPage.current && inView && fetchTemplates()
117 }, [inView, fetchTemplates, templates])
118
119 if (serverError.length) {
120 return (
121 <div className="text-left">
122 <h2 className="text-left">{__('Server error', 'extendify')}</h2>
123 <code
124 className="block max-w-xl p-4 mb-4"
125 style={{ minHeight: '10rem' }}>
126 {serverError}
127 </code>
128 <Button
129 isTertiary
130 onClick={() => resetTemplates() && fetchTemplates()}>
131 {__('Press here to reload')}
132 </Button>
133 </div>
134 )
135 }
136
137 if (nothingFound) {
138 return (
139 <div className="flex h-full items-center justify-center w-full -mt-2 sm:mt-0">
140 <h2 className="text-sm text-extendify-gray font-normal">
141 {sprintf(
142 searchParams.current.type === 'template'
143 ? __(
144 'We couldn\'t find any layouts in the "%s" category.',
145 'extendify',
146 )
147 : __(
148 'We couldn\'t find any patterns in the "%s" category.',
149 'extendify',
150 ),
151 currentTax?.title ?? currentTax.slug,
152 )}
153 </h2>
154 </div>
155 )
156 }
157
158 const breakpointColumnsObj = {
159 default: 2,
160 1320: 2,
161 860: 1,
162 599: 2,
163 400: 1,
164 }
165
166 return (
167 <>
168 {loading && (
169 <div className="flex h-full items-center justify-center w-full -mt-2 sm:mt-0">
170 <Spinner />
171 </div>
172 )}
173 <Masonry
174 breakpointCols={breakpointColumnsObj}
175 className="flex -ml-6 md:-ml-8 w-auto pb-40 pt-0.5 px-0.5 relative z-10"
176 columnClassName="pl-6 md:pl-8 bg-clip-padding min-h-screen">
177 {templates.map((template) => {
178 return (
179 <ImportTemplateBlock
180 key={template.id}
181 template={template}
182 />
183 )
184 })}
185 </Masonry>
186
187 {nextPage.current && (
188 <>
189 <div className="my-20">
190 <Spinner />
191 </div>
192 {/* This is a large div that, when in view, will trigger more patterns to load */}
193 <div
194 className="-translate-y-full flex flex-col items-end justify-end relative transform"
195 ref={loadMoreRef}
196 style={{
197 zIndex: -1,
198 marginBottom: '-200vh',
199 height: '200vh',
200 }}
201 />
202 </>
203 )}
204 </>
205 )
206 })
207