PluginProbe
Extendify / 3.1.5
Extendify v3.1.5
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 / Launch / components / SmallPreview.jsx

SmallPreview.jsx in Extendify 3.1.5, at src/Launch/components/SmallPreview.jsx

239 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import blockStyleVariations from '@launch/_data/block-style-variations.json';
2 import themeJSON from '@launch/_data/theme-processed.json';
3 import { usePreviewIframe } from '@launch/hooks/usePreviewIframe';
4 import { getFontOverrides } from '@launch/lib/preview-helpers';
5 import { hexTomatrixValues, lowerImageQuality } from '@launch/lib/util';
6 import { pageNames } from '@shared/lib/pages';
7 import { BlockPreview } from '@wordpress/block-editor';
8 import { rawHandler } from '@wordpress/blocks';
9 import {
10 useCallback,
11 useEffect,
12 useMemo,
13 useRef,
14 useState,
15 } from '@wordpress/element';
16 import { __ } from '@wordpress/i18n';
17 import classNames from 'classnames';
18 import { colord } from 'colord';
19 import { AnimatePresence, motion } from 'framer-motion';
20
21 export const SmallPreview = ({
22 style,
23 onSelect,
24 selected,
25 siteTitle,
26 showNav = true,
27 }) => {
28 const previewContainer = useRef(null);
29 const blockRef = useRef(null);
30 const observer = useRef(null);
31 const [ready, setReady] = useState(false);
32 const variation = style?.variation;
33 const theme = variation?.settings?.color?.palette?.theme;
34 const vibe = useMemo(() => style?.siteStyle?.vibe, [style?.siteStyle?.vibe]);
35 const blockVariationCSS = useMemo(() => {
36 if (vibe && blockStyleVariations[vibe]) {
37 return blockStyleVariations[vibe];
38 }
39 return blockStyleVariations['natural-1'] || '';
40 }, [vibe]);
41
42 const onLoad = useCallback(
43 (frame) => {
44 // Run this 150 times at an interval of 100ms (15s)
45 // This is a brute force check that the styles are there
46 let lastRun = performance.now();
47 let counter = 0;
48 const variationTitle = variation?.slug
49 ?.split('-')
50 .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
51 .join(' ');
52
53 const variationStyles = themeJSON[variationTitle];
54 const { customFontLinks, fontOverrides } = getFontOverrides(variation);
55
56 const checkOnStyles = () => {
57 if (counter >= 150) return;
58 const now = performance.now();
59 // Don't pass here until we've waited 100ms
60 if (now - lastRun < 100) return requestAnimationFrame(checkOnStyles);
61 lastRun = now;
62 const content = frame?.contentDocument;
63 if (content) {
64 content.querySelector('[href*=load-styles]')?.remove();
65 const siteTitleElement = content.querySelector('[href*=site-title]');
66 if (siteTitleElement.textContent !== siteTitle) {
67 siteTitleElement.textContent = siteTitle;
68 }
69 }
70 const primaryColor = theme?.find(
71 ({ slug }) => slug === 'primary',
72 )?.color;
73 const [r, g, b] = primaryColor
74 ? hexTomatrixValues(primaryColor)
75 : [0, 0, 0];
76
77 // Add custom font links if not already present
78 if (
79 customFontLinks &&
80 !frame.contentDocument?.querySelector('[id^="ext-custom-font"]')
81 ) {
82 frame.contentDocument?.head?.insertAdjacentHTML(
83 'beforeend',
84 customFontLinks,
85 );
86 }
87
88 if (!frame.contentDocument?.getElementById('ext-tj')) {
89 frame.contentDocument?.body?.insertAdjacentHTML(
90 'beforeend',
91 `<style id="ext-tj">
92 ${variationStyles}
93 ${fontOverrides}
94 ${blockVariationCSS}
95 .wp-block-missing { display: none !important }
96 img.custom-logo, [class*=wp-duotone-] img[src^="data"] {
97 filter: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"><filter id="solid-color"><feColorMatrix color-interpolation-filters="sRGB" type="matrix" values="0 0 0 0 ${r} 0 0 0 0 ${g} 0 0 0 0 ${b} 0 0 0 1 0"/></filter></svg>#solid-color') !important;
98 }
99 </style>`,
100 );
101 }
102
103 counter++;
104 requestAnimationFrame(checkOnStyles); // recursive
105 };
106 checkOnStyles();
107 },
108 [variation, theme, siteTitle, blockVariationCSS],
109 );
110
111 const { loading, ready: show } = usePreviewIframe({
112 container: blockRef.current,
113 ready,
114 onLoad,
115 // Additional time to wait after we think we are ready
116 loadDelay: 750,
117 });
118 const blocks = useMemo(() => {
119 const links = [
120 pageNames.about.title,
121 pageNames.blog.title,
122 pageNames.contact.title,
123 ];
124
125 const code = [
126 style?.headerCode,
127 style?.patterns
128 .flatMap(({ code }) => code)
129 .slice(0, 3)
130 .join('\n'),
131 style?.footerCode,
132 ]
133 .filter(Boolean)
134 .join('')
135 .replace(
136 // Replace natural-1 with dynamic vibe value
137 /natural-1/g,
138 vibe || 'natural-1',
139 )
140 .replace(
141 // <!-- wp:navigation --> <!-- /wp:navigation -->
142 /<!-- wp:navigation[.\S\s]*?\/wp:navigation -->/g,
143 showNav
144 ? `<!-- wp:paragraph {"className":"tmp-nav"} --><p class="tmp-nav" style="display: flex; gap: 2rem; margin:0;">${links.map((link) => `<span>${link}</span>`).join('')}</p ><!-- /wp:paragraph -->`
145 : '',
146 )
147 .replace(
148 // <!-- wp:navigation /-->
149 /<!-- wp:navigation.*\/-->/g,
150 showNav
151 ? `<!-- wp:paragraph {"className":"tmp-nav"} --><p class="tmp-nav" style="display: flex; gap: 2rem; margin:0;">${links.map((link) => `<span>${link}</span>`).join('')}</p ><!-- /wp:paragraph -->`
152 : '',
153 )
154 .replace(
155 /<!--\s*wp:social-links\b[^>]*>.*?<!--\s*\/wp:social-links\s*-->/gis,
156 // dont replace if showNav is true
157 (match) => (showNav ? match : ''),
158 )
159 .replace(
160 /<!-- wp:site-logo.*\/-->/g,
161 '<!-- wp:paragraph {"className":"custom-logo"} --><p class="custom-logo" style="display:flex; align-items: center; margin:0;"><img alt="" class="custom-logo" style="height: 32px;" src="https://images.extendify-cdn.com/demo-content/logos/ext-custom-logo-default.webp"></p ><!-- /wp:paragraph -->',
162 );
163 return rawHandler({ HTML: lowerImageQuality(code) });
164 }, [style?.headerCode, style?.patterns, style?.footerCode, vibe, showNav]);
165
166 useEffect(() => {
167 if (observer.current) return;
168 observer.current = new IntersectionObserver((entries) => {
169 entries[0].isIntersecting && setReady(true);
170 });
171 observer.current.observe(blockRef.current);
172 return () => observer.current.disconnect();
173 }, []);
174
175 return (
176 // biome-ignore lint: keep the button role until a refactor is done
177 <div
178 data-test="layout-preview"
179 className="relative h-full w-full overflow-hidden"
180 ref={blockRef}
181 role={onSelect ? 'button' : undefined}
182 tabIndex={onSelect ? 0 : undefined}
183 aria-label={
184 onSelect ? __('Press to select', 'extendify-local') : undefined
185 }
186 aria-selected={onSelect ? selected : undefined}
187 onKeyDown={(e) => {
188 if (!['Enter', 'Space', ' '].includes(e.key)) return;
189 onSelect?.({ ...style, variation });
190 }}
191 onClick={() => onSelect?.({ ...style, variation })}
192 >
193 {ready ? (
194 <motion.div
195 ref={previewContainer}
196 className={classNames('absolute inset-0 z-20', {
197 'opacity-0': !show,
198 })}
199 initial={{ opacity: 0 }}
200 animate={{ opacity: loading ? 0 : 1 }}
201 >
202 <BlockPreview
203 blocks={blocks}
204 viewportWidth={1400}
205 additionalStyles={[
206 // TODO: { css: themeJSON[style.variation.title] },
207 {
208 css: '.rich-text [data-rich-text-placeholder]:after { content: "" }',
209 },
210 ]}
211 />
212 </motion.div>
213 ) : null}
214 <AnimatePresence>
215 {show || (
216 <motion.div
217 initial={{ opacity: 0.7 }}
218 animate={{ opacity: 1 }}
219 exit={{ opacity: 0 }}
220 transition={{ duration: 0.5 }}
221 className="absolute inset-0 z-30"
222 style={{
223 backgroundColor: colord(
224 theme?.find(({ slug }) => slug === 'primary')?.color ?? '#ccc',
225 )
226 .alpha(0.25)
227 .toRgbString(),
228 backgroundImage:
229 'linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0) 100%)',
230 backgroundSize: '600% 600%',
231 animation: 'extendify-loading-skeleton 10s ease-in-out infinite',
232 }}
233 />
234 )}
235 </AnimatePresence>
236 </div>
237 );
238 };
239