PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
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 / Launch / components / PagePreview.jsx

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

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