PluginProbe
Extendify / 0.9.2
Extendify v0.9.2
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 0.9.2, at src/Library/components/ImportTemplateBlock.js

159 lines 6.0 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 __('Press to import %s', 'extendify'),
118 template?.fields?.type,
119 )}
120 style={{ maxHeight }}
121 className="button-focus relative m-0 cursor-pointer overflow-hidden bg-gray-100 ease-in-out"
122 onClick={importTemplate}
123 onKeyDown={handleKeyDown}>
124 <div
125 ref={importButtonRef}
126 style={{ top: topValue, transitionProperty: 'all' }}
127 className={classNames('with-light-shadow relative', {
128 [`is-template--${template.fields.status}`]:
129 template?.fields?.status && devMode,
130 'p-6 md:p-8': Number.isInteger(maxHeight),
131 })}>
132 <BlockPreview
133 blocks={blocks}
134 live={false}
135 viewportWidth={1400}
136 />
137 </div>
138 </div>
139 {/* Show dev info after the preview is loaded to trigger observer */}
140 {devMode && <DevButtonOverlay template={template} />}
141 {template?.fields?.pro && (
142 <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">
143 {__('Pro', 'extendify')}
144 </div>
145 )}
146 </div>
147 )
148 }
149
150 const halfImageSizes = (html) => {
151 return html.replace(
152 /\w+:\/\/\S*(w=(\d*))&(h=(\d*))&\w+\S*"/g,
153 (url, w, width, h, height) =>
154 url
155 .replace(w, 'w=' + Math.floor(Number(width) / 2))
156 .replace(h, 'h=' + Math.floor(Number(height) / 2)),
157 )
158 }
159