PluginProbe
Extendify / 0.4.0
Extendify v0.4.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.4.0, at src/pages/GridView.js

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