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 / components / ImportTemplateBlock.js

ImportTemplateBlock.js in Extendify 0.1.0, at src/components/ImportTemplateBlock.js

249 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useEffect, useState, useRef, memo } from '@wordpress/element'
2 import { __, sprintf } from '@wordpress/i18n'
3 import { BlockPreview } from '@wordpress/block-editor'
4 import { rawHandler } from '@wordpress/blocks'
5 import { Icon } from '@wordpress/icons'
6 import { Button } from '@wordpress/components'
7 import { useInView } from 'react-intersection-observer'
8 import {
9 growthArrow,
10 patterns,
11 layouts,
12 support,
13 star,
14 brandLogo,
15 } from '../components/icons/'
16 import { AuthorizationCheck, Middleware } from '../middleware'
17 import { injectTemplateBlocks } from '../util/templateInjection'
18 import { useUserStore } from '../state/User'
19 import { useGlobalStore } from '../state/GlobalState'
20 import { Templates as TemplatesApi } from '../api/Templates'
21 import Primary from './buttons/Primary'
22 import SplitModal from './modals/SplitModal'
23 import SettingsModal from './modals/SettingsModal'
24 import { useIsDevMode } from '../hooks/helpers'
25 import { DevButtonOverlay } from './DevHelpers'
26
27 const LiveBlocksMemoized = memo(
28 ({ blocks }) => {
29 return (
30 <div className="with-light-shadow relative">
31 <BlockPreview
32 blocks={blocks}
33 live={false}
34 viewportWidth={1400}
35 />
36 </div>
37 )
38 },
39 (oldBlocks, newBlocks) => oldBlocks.clientId == newBlocks.clientId,
40 )
41 const canImportMiddleware = Middleware([
42 'NeedsRegistrationModal',
43 'hasRequiredPlugins',
44 'hasPluginsActivated',
45 ])
46
47 // TODO: extract this modal
48 const NoImportModal = (
49 <SplitModal
50 isOpen={true}
51 onRequestClose={() => useGlobalStore.setState({ currentModal: null })}
52 left={
53 <>
54 <div className="flex space-x-2 items-center justify-center mb-10 text-extendify-black">
55 {brandLogo}
56 </div>
57
58 <h3 className="text-xl md:leading-3">
59 {__("You're out of imports", 'extendify')}
60 </h3>
61 <p className="text-sm text-black">
62 {__(
63 'Sign up today and get unlimited access to our entire collection of patterns and page layouts.',
64 'extendify',
65 )}
66 </p>
67 <div>
68 <Primary
69 tagName="a"
70 target="_blank"
71 className="m-auto mt-10 py-3"
72 href={`https://extendify.com/pricing/?utm_source=${window.extendifyData.sdk_partner}&utm_medium=library&utm_campaign=no-imports-modal&utm_content=get-unlimited-imports`}
73 rel="noreferrer">
74 {__('Get Unlimited Imports', 'extendify')}
75 <Icon
76 icon={growthArrow}
77 size={24}
78 className="-ml-1 mr-1"
79 />
80 </Primary>
81 <p className="text-sm text-extendify-gray mb-0">
82 {__('Have an account?', 'extendify')}
83 <Button
84 onClick={() => {
85 useGlobalStore.setState({
86 currentModal: (
87 <SettingsModal
88 isOpen={true}
89 onClose={() =>
90 useGlobalStore.setState({
91 currentModal: null,
92 })
93 }
94 />
95 ),
96 })
97 }}
98 className="underline hover:no-underline text-sm text-extendify-gray pl-2">
99 {__('Sign in', 'extendify')}
100 </Button>
101 </p>
102 </div>
103 </>
104 }
105 right={
106 <div className="space-y-2">
107 <div className="flex items-center space-x-2">
108 <Icon icon={patterns} size={24} className="-ml-1 mr-1" />
109 <span className="text-sm leading-none">
110 {__("Access to 100's of Patterns", 'extendify')}
111 </span>
112 </div>
113 <div className="flex items-center space-x-2">
114 <Icon icon={layouts} size={24} className="-ml-1 mr-1" />
115 <span className="text-sm leading-none">
116 {__('Beautiful full page layouts', 'extendify')}
117 </span>
118 </div>
119 <div className="flex items-center space-x-2">
120 <Icon icon={support} size={24} className="-ml-1 mr-1" />
121 <span className="text-sm leading-none">
122 {__('Fast and friendly support', 'extendify')}
123 </span>
124 </div>
125 <div className="flex items-center space-x-2">
126 <Icon icon={star} size={24} className="-ml-1 mr-1" />
127 <span className="text-sm leading-none">
128 {__('14-Day guarantee', 'extendify')}
129 </span>
130 </div>
131 </div>
132 }
133 />
134 )
135
136 export function ImportTemplateBlock({ template }) {
137 const importButtonRef = useRef(null)
138 const once = useRef(false)
139 const canImport = useUserStore((state) => state.canImport)
140 const setOpen = useGlobalStore((state) => state.setOpen)
141 const setCurrentModal = useGlobalStore((state) => state.setCurrentModal)
142 const blocks = rawHandler({ HTML: template.fields.code })
143 const [hasBeenSeen, setHasBeenSeen] = useState(false)
144 const [loaded, setLoaded] = useState(false)
145 const [onlyLoadInView, inView] = useInView()
146 const devMode = useIsDevMode()
147
148 const focusTrapInnerBlocks = () => {
149 if (once.current) return
150 once.current = true
151 Array.from(
152 importButtonRef.current.querySelectorAll(
153 'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])',
154 ),
155 ).forEach((el) => el.setAttribute('tabIndex', '-1'))
156 }
157
158 const importTemplates = async () => {
159 await canImportMiddleware.check(template)
160 AuthorizationCheck(canImportMiddleware)
161 .then(() => {
162 setTimeout(() => {
163 injectTemplateBlocks(blocks, template)
164 .then(() => setCurrentModal(null))
165 .then(() => setOpen(false))
166 .then(() => canImportMiddleware.reset())
167 }, 100)
168 })
169 .catch(() => {})
170 }
171
172 const handleKeyDown = (event) => {
173 if (['Enter', 'Space', ' '].includes(event.key)) {
174 event.stopPropagation()
175 event.preventDefault()
176 importTemplate()
177 }
178 }
179
180 const importTemplate = () => {
181 if (!canImport()) {
182 setCurrentModal(NoImportModal)
183 return
184 }
185 TemplatesApi.maybeImport(template)
186 importTemplates()
187 }
188
189 // Trigger resize event on the live previews to add
190 // Grammerly/Loom/etc compatability
191 // TODO: This can probably be removed after WP 5.9
192 useEffect(() => {
193 const rafIds = []
194 let rafId1, rafId2, rafId3
195 rafId1 = window.requestAnimationFrame(() => {
196 rafId2 = window.requestAnimationFrame(() => {
197 importButtonRef.current
198 .querySelectorAll('iframe')
199 .forEach((frame) => {
200 const rafId = window.requestAnimationFrame(() => {
201 frame.contentWindow.dispatchEvent(
202 new Event('resize'),
203 )
204 })
205 rafIds.push(rafId)
206 })
207 rafId3 = window.requestAnimationFrame(() => {
208 window.dispatchEvent(new Event('resize'))
209 setLoaded(true)
210 })
211 })
212 })
213 return () =>
214 [...rafIds, rafId1, rafId2, rafId3].forEach((id) =>
215 window.cancelAnimationFrame(id),
216 )
217 }, [])
218
219 useEffect(() => {
220 if (!hasBeenSeen && inView) {
221 setHasBeenSeen(true)
222 }
223 }, [inView, hasBeenSeen, template])
224
225 return (
226 <div className="relative group">
227 <div
228 role="button"
229 tabIndex="0"
230 ref={importButtonRef}
231 aria-label={sprintf(
232 __('Press to import %s', 'extendify'),
233 template?.fields?.type,
234 )}
235 className="mb-6 md:mb-8 cursor-pointer button-focus"
236 onFocus={focusTrapInnerBlocks}
237 onClick={importTemplate}
238 onKeyDown={handleKeyDown}>
239 <div
240 ref={onlyLoadInView}
241 className="invisible absolute inset-0 pointer-events-none"></div>
242 {hasBeenSeen && <LiveBlocksMemoized blocks={blocks} />}
243 </div>
244 {/* Show dev info after the preview is loaded to trigger observer */}
245 {devMode && loaded && <DevButtonOverlay template={template} />}
246 </div>
247 )
248 }
249