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

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

202 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import classNames from 'classnames'
2 import { useEffect, useState, useRef, useMemo } from '@wordpress/element'
3 import { __, sprintf } from '@wordpress/i18n'
4 import { BlockPreview } from '@wordpress/block-editor'
5 import { rawHandler } from '@wordpress/blocks'
6 import { AuthorizationCheck, Middleware } from '../middleware'
7 import { injectTemplateBlocks } from '../util/templateInjection'
8 import { useUserStore } from '../state/User'
9 import { useGlobalStore } from '../state/GlobalState'
10 import { Templates as TemplatesApi } from '../api/Templates'
11 import { useIsDevMode } from '../hooks/helpers'
12 import { DevButtonOverlay } from './DevHelpers'
13 import { NoImportModal } from './modals/NoImportModal'
14 import { ProModal } from './modals/ProModal'
15
16 const canImportMiddleware = Middleware([
17 'hasRequiredPlugins',
18 'hasPluginsActivated',
19 ])
20
21 export function ImportTemplateBlock({ template, maxHeight }) {
22 const importButtonRef = useRef(null)
23 const once = useRef(false)
24 const hasAvailableImports = useUserStore(
25 (state) => state.hasAvailableImports,
26 )
27 const loggedIn = useUserStore((state) => state.apiKey.length)
28 const setOpen = useGlobalStore((state) => state.setOpen)
29 const pushModal = useGlobalStore((state) => state.pushModal)
30 const removeAllModals = useGlobalStore((state) => state.removeAllModals)
31 const blocks = useMemo(
32 () => rawHandler({ HTML: template.fields.code }),
33 [template.fields.code],
34 )
35 const [loaded, setLoaded] = useState(false)
36 const devMode = useIsDevMode()
37 const [topValue, setTopValue] = useState(0)
38
39 const focusTrapInnerBlocks = () => {
40 if (once.current) return
41 once.current = true
42 Array.from(
43 importButtonRef.current.querySelectorAll(
44 'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])',
45 ),
46 ).forEach((el) => el.setAttribute('tabIndex', '-1'))
47 }
48
49 const importTemplates = async () => {
50 await canImportMiddleware.check(template)
51 AuthorizationCheck(canImportMiddleware)
52 .then(() => {
53 setTimeout(() => {
54 injectTemplateBlocks(blocks, template)
55 .then(() => removeAllModals())
56 .then(() => setOpen(false))
57 .then(() => canImportMiddleware.reset())
58 }, 100)
59 })
60 .catch(() => {})
61 }
62
63 const handleKeyDown = (event) => {
64 if (['Enter', 'Space', ' '].includes(event.key)) {
65 event.stopPropagation()
66 event.preventDefault()
67 importTemplate()
68 }
69 }
70
71 const importTemplate = () => {
72 // Make a note that they attempted to import
73 TemplatesApi.maybeImport(template)
74
75 if (template?.fields?.pro && !loggedIn) {
76 pushModal(<ProModal />)
77 return
78 }
79 if (!hasAvailableImports()) {
80 pushModal(<NoImportModal />)
81 return
82 }
83
84 importTemplates()
85 }
86
87 // Trigger resize event on the live previews to add
88 // Grammerly/Loom/etc compatability
89 // TODO: This can probably be removed after WP 5.9
90 useEffect(() => {
91 const rafIds = []
92 const timeouts = []
93 let rafId1, rafId2, rafId3, rafId4
94 rafId1 = window.requestAnimationFrame(() => {
95 rafId2 = window.requestAnimationFrame(() => {
96 importButtonRef.current
97 .querySelectorAll('iframe')
98 .forEach((frame) => {
99 const inner = frame.contentWindow.document.body
100 const rafId = window.requestAnimationFrame(() => {
101 const maybeRoot =
102 inner.querySelector('.is-root-container')
103 if (maybeRoot) {
104 const height = maybeRoot?.offsetHeight
105 if (height) {
106 rafId4 = window.requestAnimationFrame(
107 () => {
108 frame.style.height = height + 'px'
109 },
110 )
111 const id = window.setTimeout(() => {
112 frame.style.height = height + 'px'
113 }, 2000)
114 timeouts.push(id)
115 }
116 }
117 frame.contentWindow.dispatchEvent(
118 new Event('resize'),
119 )
120 })
121 rafIds.push(rafId)
122 })
123 rafId3 = window.requestAnimationFrame(() => {
124 window.dispatchEvent(new Event('resize'))
125 setLoaded(true)
126 })
127 })
128 })
129 return () => {
130 ;[...rafIds, rafId1, rafId2, rafId3, rafId4].forEach((id) =>
131 window.cancelAnimationFrame(id),
132 )
133 timeouts.forEach((id) => window.clearTimeout(id))
134 }
135 }, [])
136
137 useEffect(() => {
138 if (!Number.isInteger(maxHeight)) return
139 const button = importButtonRef.current
140 const handleIn = () => {
141 // The live component changes over time so easier to query on demand
142 const height = button.offsetHeight
143 button.style.transitionDuration = height * 1.5 + 'ms'
144 setTopValue(Math.abs(height - maxHeight) * -1)
145 }
146 const handleOut = () => {
147 const height = button.offsetHeight
148 button.style.transitionDuration = height / 1.5 + 'ms'
149 setTopValue(0)
150 }
151 button.addEventListener('focus', handleIn)
152 button.addEventListener('mouseenter', handleIn)
153 button.addEventListener('blur', handleOut)
154 button.addEventListener('mouseleave', handleOut)
155 return () => {
156 button.removeEventListener('focus', handleIn)
157 button.removeEventListener('mouseenter', handleIn)
158 button.removeEventListener('blur', handleOut)
159 button.removeEventListener('mouseleave', handleOut)
160 }
161 }, [maxHeight])
162
163 return (
164 <div className="relative group">
165 <div
166 role="button"
167 tabIndex="0"
168 aria-label={sprintf(
169 __('Press to import %s', 'extendify'),
170 template?.fields?.type,
171 )}
172 style={{ maxHeight }}
173 className="m-0 cursor-pointer button-focus ease-in-out relative overflow-hidden bg-gray-100"
174 onFocus={focusTrapInnerBlocks}
175 onClick={importTemplate}
176 onKeyDown={handleKeyDown}>
177 <div
178 ref={importButtonRef}
179 style={{ top: topValue, transitionProperty: 'all' }}
180 className={classNames('with-light-shadow relative', {
181 [`is-template--${template.fields.status}`]:
182 template?.fields?.status && devMode,
183 'p-6 md:p-8': Number.isInteger(maxHeight),
184 })}>
185 <BlockPreview
186 blocks={blocks}
187 live={false}
188 viewportWidth={1400}
189 />
190 </div>
191 </div>
192 {/* Show dev info after the preview is loaded to trigger observer */}
193 {devMode && loaded && <DevButtonOverlay template={template} />}
194 {template?.fields?.pro && (
195 <div className="bg-white bg-wp-theme-500 border font-medium border-none absolute z-20 top-4 right-4 py-1 px-2.5 rounded-md shadow-sm no-underline text-white pointer-events-none">
196 {__('Pro', 'extendify')}
197 </div>
198 )}
199 </div>
200 )
201 }
202