PluginProbe
Extendify / 1.17.1
Extendify v1.17.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 0.7.0 All 126 releases
extendify / src / Launch / components / SmallPreview.jsx

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

205 lines 6.4 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 {
4 useState,
5 useRef,
6 useCallback,
7 useEffect,
8 useMemo,
9 } from '@wordpress/element';
10 import { __ } from '@wordpress/i18n';
11 import { pageNames } from '@shared/lib/pages';
12 import classNames from 'classnames';
13 import { colord } from 'colord';
14 import { AnimatePresence, motion } from 'framer-motion';
15 import themeJSON from '@launch/_data/theme-processed.json';
16 import { usePreviewIframe } from '@launch/hooks/usePreviewIframe';
17 import { getFontOverrides } from '@launch/lib/preview-helpers';
18 import { hexTomatrixValues, lowerImageQuality } from '@launch/lib/util';
19
20 export const SmallPreview = ({ style, onSelect, selected, siteTitle }) => {
21 const previewContainer = useRef(null);
22 const blockRef = useRef(null);
23 const observer = useRef(null);
24 const [ready, setReady] = useState(false);
25 const variation = style?.variation;
26 const theme = variation?.settings?.color?.palette?.theme;
27
28 const onLoad = useCallback(
29 (frame) => {
30 // Run this 150 times at an interval of 100ms (15s)
31 // This is a brute force check that the styles are there
32 let lastRun = performance.now();
33 let counter = 0;
34
35 const variationStyles = themeJSON[variation?.title];
36 const { customFontLinks, fontOverrides } = getFontOverrides(variation);
37
38 const checkOnStyles = () => {
39 if (counter >= 150) return;
40 const now = performance.now();
41 if (now - lastRun < 100) return requestAnimationFrame(checkOnStyles);
42 lastRun = now;
43 const content = frame?.contentDocument;
44 if (content) {
45 content.querySelector('[href*=load-styles]')?.remove();
46 const siteTitleElement = content.querySelector('[href*=site-title]');
47 if (siteTitleElement) siteTitleElement.textContent = siteTitle;
48 }
49 const primaryColor = theme?.find(
50 ({ slug }) => slug === 'primary',
51 )?.color;
52 const [r, g, b] = primaryColor
53 ? hexTomatrixValues(primaryColor)
54 : [0, 0, 0];
55
56 // Add custom font links if not already present
57 if (
58 customFontLinks &&
59 !frame.contentDocument?.querySelector('[id^="ext-custom-font"]')
60 ) {
61 frame.contentDocument?.head?.insertAdjacentHTML(
62 'beforeend',
63 customFontLinks,
64 );
65 }
66
67 if (!frame.contentDocument?.getElementById('ext-tj')) {
68 frame.contentDocument?.body?.insertAdjacentHTML(
69 'beforeend',
70 `<style id="ext-tj">
71 ${variationStyles}
72 ${fontOverrides}
73 .wp-block-missing { display: none !important }
74 img.custom-logo, [class*=wp-duotone-] img[src^="data"] {
75 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;
76 }
77 </style>`,
78 );
79 }
80
81 counter++;
82 requestAnimationFrame(checkOnStyles); // recursive
83 };
84 checkOnStyles();
85 },
86 [variation, theme, siteTitle],
87 );
88
89 const { loading, ready: show } = usePreviewIframe({
90 container: blockRef.current,
91 ready,
92 onLoad,
93 loadDelay: 2000,
94 });
95 const blocks = useMemo(() => {
96 const links = [
97 pageNames.about.title,
98 pageNames.blog.title,
99 pageNames.contact.title,
100 ];
101
102 const code = [
103 style?.headerCode,
104 style?.patterns
105 .map(({ code }) => code)
106 .flat()
107 .slice(0, 3)
108 .join('\n'),
109 style?.footerCode,
110 ]
111 .filter(Boolean)
112 .join('')
113 .replace(
114 // <!-- wp:navigation --> <!-- /wp:navigation -->
115 /<!-- wp:navigation[.\S\s]*?\/wp:navigation -->/g,
116 `<!-- wp:paragraph {"className":"tmp-nav"} --><p class="tmp-nav" style="word-spacing: 1.25rem;">${links.join(' ')}</p ><!-- /wp:paragraph -->`,
117 )
118 .replace(
119 // <!-- wp:navigation /-->
120 /<!-- wp:navigation.*\/-->/g,
121 `<!-- wp:paragraph {"className":"tmp-nav"} --><p class="tmp-nav" style="word-spacing: 1.25rem;">${links.join(' ')}</p ><!-- /wp:paragraph -->`,
122 )
123 .replace(
124 /<!-- wp:site-logo.*\/-->/g,
125 '<!-- wp:paragraph {"className":"custom-logo"} --><p class="custom-logo" style="display:flex; align-items: center;"><img alt="" class="custom-logo" style="height: 32px;" src="https://assets.extendify.com/demo-content/logos/extendify-demo-logo.png"></p ><!-- /wp:paragraph -->',
126 );
127 return rawHandler({ HTML: lowerImageQuality(code) });
128 }, [style]);
129
130 useEffect(() => {
131 if (observer.current) return;
132 observer.current = new IntersectionObserver((entries) => {
133 entries[0].isIntersecting && setReady(true);
134 });
135 observer.current.observe(blockRef.current);
136 return () => observer.current.disconnect();
137 }, []);
138
139 return (
140 <>
141 <div
142 data-test="layout-preview"
143 className="relative h-full w-full overflow-hidden"
144 ref={blockRef}
145 role={onSelect ? 'button' : undefined}
146 tabIndex={onSelect ? 0 : undefined}
147 aria-label={
148 onSelect ? __('Press to select', 'extendify-local') : undefined
149 }
150 aria-selected={onSelect ? selected : undefined}
151 onKeyDown={(e) => {
152 if (['Enter', 'Space', ' '].includes(e.key)) {
153 onSelect && onSelect({ ...style, variation });
154 }
155 }}
156 onClick={onSelect ? () => onSelect({ ...style, variation }) : () => {}}>
157 {ready ? (
158 <motion.div
159 ref={previewContainer}
160 className={classNames('absolute inset-0 z-20', {
161 'opacity-0': !show,
162 })}
163 initial={{ opacity: 0 }}
164 animate={{ opacity: loading ? 0 : 1 }}>
165 <BlockPreview
166 blocks={blocks}
167 viewportWidth={1400}
168 additionalStyles={[
169 // TODO: { css: themeJSON[style.variation.title] },
170 {
171 css: '.rich-text [data-rich-text-placeholder]:after { content: "" }',
172 },
173 ]}
174 />
175 </motion.div>
176 ) : null}
177 <AnimatePresence>
178 {show || (
179 <motion.div
180 initial={{ opacity: 0.7 }}
181 animate={{ opacity: 1 }}
182 exit={{ opacity: 0 }}
183 transition={{ duration: 0.5 }}
184 className="absolute inset-0 z-30"
185 style={{
186 backgroundColor: colord(
187 theme?.find(({ slug }) => slug === 'primary')?.color ??
188 '#ccc',
189 )
190 .alpha(0.25)
191 .toRgbString(),
192 backgroundImage:
193 'linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0) 100%)',
194 backgroundSize: '600% 600%',
195 animation:
196 'extendify-loading-skeleton 10s ease-in-out infinite',
197 }}
198 />
199 )}
200 </AnimatePresence>
201 </div>
202 </>
203 );
204 };
205