PluginProbe
Extendify / 1.9.3
Extendify v1.9.3
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 0.7.0 All 126 releases
extendify / src / Library / components / ImportTemplateBlock.js

ImportTemplateBlock.js in Extendify 1.9.3, at src/Library/components/ImportTemplateBlock.js

160 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { BlockPreview } from '@wordpress/block-editor'
2 import { rawHandler } from '@wordpress/blocks'
3 import { useRef, useMemo, useEffect, useState } from '@wordpress/element'
4 import { __, sprintf } from '@wordpress/i18n'
5 import classNames from 'classnames'
6 import { Templates as TemplatesApi } from '@library/api/Templates'
7 import { useIsDevMode } from '@library/hooks/helpers'
8 import { AuthorizationCheck, Middleware } from '@library/middleware'
9 import { useGlobalStore } from '@library/state/GlobalState'
10 import { useUserStore } from '@library/state/User'
11 import { injectTemplateBlocks } from '@library/util/templateInjection'
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 hasAvailableImports = useUserStore(
24 (state) => state.hasAvailableImports,
25 )
26 const loggedIn = useUserStore((state) => state.apiKey.length)
27 const setOpen = useGlobalStore((state) => state.setOpen)
28 const pushModal = useGlobalStore((state) => state.pushModal)
29 const removeAllModals = useGlobalStore((state) => state.removeAllModals)
30 const [topValue, setTopValue] = useState(0)
31 const type = Array.isArray(template?.fields?.type)
32 ? template.fields.type[0]
33 : template?.fields?.type
34 const blocks = useMemo(
35 () => rawHandler({ HTML: halfImageSizes(template.fields.code) }),
36 [template.fields.code],
37 )
38 // The above will cut the image sizes in half, and the below will be inserted into the page
39 const blocksRaw = useMemo(
40 () => rawHandler({ HTML: template.fields.code }),
41 [template.fields.code],
42 )
43 const devMode = useIsDevMode()
44
45 const importTemplates = async () => {
46 await canImportMiddleware.check(template)
47 AuthorizationCheck(canImportMiddleware)
48 .then(() => {
49 setTimeout(() => {
50 injectTemplateBlocks(blocksRaw, template)
51 .then(() => removeAllModals())
52 .then(() => setOpen(false))
53 .then(() => canImportMiddleware.reset())
54 }, 100)
55 })
56 .catch(() => {})
57 }
58
59 const handleKeyDown = (event) => {
60 if (['Enter', 'Space', ' '].includes(event.key)) {
61 event.stopPropagation()
62 event.preventDefault()
63 importTemplate()
64 }
65 }
66
67 const importTemplate = () => {
68 // Make a note that they attempted to import
69 TemplatesApi.maybeImport(template)
70
71 if (template?.fields?.pro && !loggedIn) {
72 pushModal(<ProModal />)
73 return
74 }
75 if (!hasAvailableImports()) {
76 pushModal(<NoImportModal />)
77 return
78 }
79
80 importTemplates()
81 }
82
83 // Handle layout animation
84 useEffect(() => {
85 if (!Number.isInteger(maxHeight)) return
86 if (type !== 'layout') return
87 const button = importButtonRef.current
88 const handleIn = () => {
89 // The live component changes over time so easier to query on demand
90 const height = button.offsetHeight
91 button.style.transitionDuration = height * 1.5 + 'ms'
92 setTopValue(Math.abs(height - maxHeight) * -1)
93 }
94 const handleOut = () => {
95 const height = button.offsetHeight
96 button.style.transitionDuration = height / 1.5 + 'ms'
97 setTopValue(0)
98 }
99 button.addEventListener('focus', handleIn)
100 button.addEventListener('mouseenter', handleIn)
101 button.addEventListener('blur', handleOut)
102 button.addEventListener('mouseleave', handleOut)
103 return () => {
104 button.removeEventListener('focus', handleIn)
105 button.removeEventListener('mouseenter', handleIn)
106 button.removeEventListener('blur', handleOut)
107 button.removeEventListener('mouseleave', handleOut)
108 }
109 }, [maxHeight, type])
110
111 return (
112 <div className="group relative">
113 <div
114 role="button"
115 tabIndex="0"
116 aria-label={sprintf(
117 // translators: %s is the type of template (e.g. layout, pattern)
118 __('Press to import %s', 'extendify'),
119 template?.fields?.type,
120 )}
121 style={{ maxHeight }}
122 className="button-focus relative m-0 cursor-pointer overflow-hidden bg-gray-100 ease-in-out"
123 onClick={importTemplate}
124 onKeyDown={handleKeyDown}>
125 <div
126 ref={importButtonRef}
127 style={{ top: topValue, transitionProperty: 'all' }}
128 className={classNames('with-light-shadow relative', {
129 [`is-template--${template.fields.status}`]:
130 template?.fields?.status && devMode,
131 'p-6 md:p-8': Number.isInteger(maxHeight),
132 })}>
133 <BlockPreview
134 blocks={blocks}
135 live={false}
136 viewportWidth={1400}
137 />
138 </div>
139 </div>
140 {/* Show dev info after the preview is loaded to trigger observer */}
141 {devMode && <DevButtonOverlay template={template} />}
142 {template?.fields?.pro && !loggedIn && (
143 <div className="pointer-events-none absolute top-4 right-4 z-20 rounded-md border border-none bg-white bg-wp-theme-500 py-1 px-2.5 font-medium text-white no-underline shadow-sm">
144 {__('Pro', 'extendify')}
145 </div>
146 )}
147 </div>
148 )
149 }
150
151 const halfImageSizes = (html) => {
152 return html.replace(
153 /\w+:\/\/\S*(w=(\d*))&(h=(\d*))&\w+\S*"/g,
154 (url, w, width, h, height) =>
155 url
156 .replace(w, 'w=' + Math.floor(Number(width) / 2))
157 .replace(h, 'h=' + Math.floor(Number(height) / 2)),
158 )
159 }
160