PluginProbe
Extendify / 0.1.0
Extendify v0.1.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.1.0, at src/pages/GridView.js

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