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 / PagePreview.jsx

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

255 lines 8.1 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 { Spinner } from '@wordpress/components';
10 import {
11 forwardRef,
12 useCallback,
13 useLayoutEffect,
14 useMemo,
15 useRef,
16 useState,
17 } from '@wordpress/element';
18 import classNames from 'classnames';
19 import { AnimatePresence, motion } from 'framer-motion';
20
21 export const PagePreview = forwardRef(
22 ({ style, siteTitle, loading, showNav = true }, ref) => {
23 const previewContainer = useRef(null);
24 const blockRef = useRef(null);
25 const [ready, setReady] = useState(false);
26 const variation = style?.variation;
27 const theme = variation?.settings?.color?.palette?.theme;
28 const vibe = useMemo(
29 () => style?.siteStyle?.vibe,
30 [style?.siteStyle?.vibe],
31 );
32 const blockVariationCSS = useMemo(() => {
33 if (vibe && blockStyleVariations[vibe]) {
34 return blockStyleVariations[vibe];
35 }
36 return blockStyleVariations['natural-1'] || '';
37 }, [vibe]);
38
39 const onLoad = useCallback(
40 (frame) => {
41 frame.contentDocument?.getElementById('ext-tj')?.remove();
42 // Run this 150 times at an interval of 100ms (15s)
43 // This is a brute force check that the styles are there
44 let lastRun = performance.now();
45 let counter = 0;
46 const variationTitle = variation?.slug
47 ?.split('-')
48 .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
49 .join(' ');
50
51 const variationStyles = themeJSON[variationTitle];
52 const { customFontLinks, fontOverrides } = getFontOverrides(variation);
53
54 const primaryColor = theme?.find(
55 ({ slug }) => slug === 'primary',
56 )?.color;
57 const [r, g, b] = hexTomatrixValues(primaryColor);
58
59 const checkOnStyles = () => {
60 if (counter >= 150) return;
61 const now = performance.now();
62 if (now - lastRun < 100) return requestAnimationFrame(checkOnStyles);
63 lastRun = now;
64 const content = frame?.contentDocument;
65 if (content) {
66 content.querySelector('[href*=load-styles]')?.remove();
67 const siteTitleElement =
68 content.querySelectorAll('[href*=site-title]');
69 siteTitleElement?.forEach((element) => {
70 element.textContent = siteTitle;
71 });
72 }
73
74 // Add custom font links if not already present
75 if (
76 customFontLinks &&
77 !frame.contentDocument?.querySelector('[id^="ext-custom-font"]')
78 ) {
79 frame.contentDocument?.head?.insertAdjacentHTML(
80 'beforeend',
81 customFontLinks,
82 );
83 }
84
85 if (!frame.contentDocument?.getElementById('ext-tj')) {
86 frame.contentDocument?.body?.insertAdjacentHTML(
87 'beforeend',
88 `<style id="ext-tj">
89 .wp-block-missing { display: none !important }
90 img.custom-logo, [class*=wp-duotone-] img[src^="data"] {
91 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;
92 }
93 ${variationStyles}
94 ${blockVariationCSS}
95 ${fontOverrides}
96 </style>`,
97 );
98 }
99
100 // Look for any frames inside the iframe, like the html block
101 const innerFrames = frame.contentDocument?.querySelectorAll('iframe');
102 innerFrames?.forEach((inner) => {
103 inner?.contentDocument
104 ?.querySelector('[href*=load-styles]')
105 ?.remove();
106 inner?.contentDocument
107 ?.querySelector('body')
108 ?.classList.add('editor-styles-wrapper');
109
110 // Add custom font links to inner frames if not already present
111 if (
112 customFontLinks &&
113 !inner.contentDocument?.querySelector('[id^="ext-custom-font"]')
114 ) {
115 inner.contentDocument?.head?.insertAdjacentHTML(
116 'beforeend',
117 customFontLinks,
118 );
119 }
120
121 if (inner && !inner.contentDocument?.getElementById('ext-tj')) {
122 inner.contentDocument?.body?.insertAdjacentHTML(
123 'beforeend',
124 `<style id="ext-tj">
125 body { background-color: transparent !important; }
126 body, body * { box-sizing: border-box !important; }
127 ${variationStyles}
128 ${blockVariationCSS}
129 ${fontOverrides}
130 </style>`,
131 );
132 }
133 });
134
135 counter++;
136 requestAnimationFrame(checkOnStyles); // recursive
137 };
138 checkOnStyles();
139 },
140 [variation, theme, siteTitle, blockVariationCSS],
141 );
142
143 const { ready: showPreview } = usePreviewIframe({
144 container: ref.current,
145 ready,
146 onLoad,
147 loadDelay: 400,
148 });
149
150 const blocks = useMemo(() => {
151 const links = [
152 pageNames.about.title,
153 pageNames.blog.title,
154 pageNames.contact.title,
155 ];
156
157 const code = [
158 style?.headerCode,
159 style?.patterns?.flatMap(({ code }) => code).join(''),
160 style?.footerCode,
161 ]
162 .filter(Boolean)
163 .join('')
164 .replace(
165 // Replace natural-1 with dynamic vibe value
166 /natural-1/g,
167 vibe || 'natural-1',
168 )
169 .replace(
170 // <!-- wp:navigation --> <!-- /wp:navigation -->
171 /<!-- wp:navigation[.\S\s]*?\/wp:navigation -->/g,
172 showNav
173 ? `<!-- 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 -->`
174 : '',
175 )
176 .replace(
177 // <!-- wp:navigation /-->
178 /<!-- wp:navigation.*\/-->/g,
179 showNav
180 ? `<!-- 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 -->`
181 : '',
182 )
183 .replace(
184 /<!--\s*wp:social-links\b[^>]*>.*?<!--\s*\/wp:social-links\s*-->/gis,
185 // dont replace if showNav is true
186 (match) => (showNav ? match : ''),
187 )
188 .replace(
189 /<!-- wp:site-logo.*\/-->/g,
190 '<!-- 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 -->',
191 );
192 return rawHandler({ HTML: lowerImageQuality(code) });
193 }, [style?.headerCode, style?.patterns, style?.footerCode, vibe, showNav]);
194
195 useLayoutEffect(() => {
196 setReady(false);
197 const timer = setTimeout(() => setReady(true), 0);
198 return () => clearTimeout(timer);
199 }, [blocks]);
200
201 const isLoading = !showPreview && loading;
202
203 return (
204 <>
205 <AnimatePresence>
206 {(isLoading || !showPreview) && (
207 <motion.div
208 initial={{ opacity: 0.7 }}
209 animate={{ opacity: 1 }}
210 exit={{ opacity: 0 }}
211 transition={{ duration: 0.3 }}
212 className="pointer-events-none absolute inset-0 z-30"
213 style={{
214 // opacity: showPreview || !ready ? 0 : 1,
215 backgroundColor: 'rgba(204, 204, 204, 0.25)',
216 backgroundImage:
217 'linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0) 100%)',
218 backgroundSize: '600% 600%',
219 animation:
220 'extendify-loading-skeleton 10s ease-in-out infinite',
221 }}
222 >
223 <div className="absolute inset-0 flex items-center justify-center">
224 <Spinner className="h-10 w-10 text-design-main" />
225 </div>
226 </motion.div>
227 )}
228 </AnimatePresence>
229 <div
230 data-test="layout-preview"
231 ref={blockRef}
232 className={classNames('group z-10 w-full bg-transparent', {
233 'opacity-0': !showPreview,
234 })}
235 >
236 <div
237 ref={previewContainer}
238 className="relative m-auto max-w-[1440px] rounded-lg"
239 >
240 <BlockPreview
241 blocks={blocks}
242 viewportWidth={1440}
243 additionalStyles={[
244 {
245 css: '.rich-text [data-rich-text-placeholder]:after { content: "" }',
246 },
247 ]}
248 />
249 </div>
250 </div>
251 </>
252 );
253 },
254 );
255