PluginProbe
Extendify / 3.0.4
Extendify v3.0.4
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 / Agent / workflows / theme / components / change-site-design / DesignOption.jsx

DesignOption.jsx in Extendify 3.0.4, at src/Agent/workflows/theme/components/change-site-design/DesignOption.jsx

158 lines 4.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useIframeScale } from '@agent/hooks/useIframeScale';
2 import { removeAnimationClasses } from '@agent/workflows/theme/components/change-site-design/utils/removeAnimationClasses';
3 import { useMemo, useRef } from '@wordpress/element';
4 import { __ } from '@wordpress/i18n';
5 import classnames from 'classnames';
6 import { colord } from 'colord';
7
8 const VIEWPORT_WIDTH = Math.max(window.innerWidth, 1400);
9 const VIEWPORT_HEIGHT =
10 (document.querySelector('header')?.offsetHeight ?? 0) +
11 (document.querySelector('.ext-hero-section')?.offsetHeight ?? 0) ||
12 window.innerHeight;
13
14 const lowerImageQuality = (html) =>
15 html.replace(
16 /(https?:\/\/\S+\?w=\d+)/gi,
17 '$1&q=10&auto=format,compress&fm=avif',
18 );
19
20 // Clone duotone SVG filters from the page and adjust colors for this variation
21 const getDuotoneSvgNodes = (duotoneTheme) => {
22 const duotoneMap = new Map(
23 (duotoneTheme ?? []).map((item) => [item.slug, item]),
24 );
25
26 return [
27 ...document.querySelectorAll('svg:has(filter[id^="wp-duotone"])'),
28 ].map((svg) => {
29 const cloned = svg.cloneNode(true);
30
31 cloned.querySelectorAll('filter[id^="wp-duotone"]').forEach((filter) => {
32 const preset = duotoneMap.get(filter.id.replace('wp-duotone-', ''));
33
34 if (!preset?.colors || preset.colors.length !== 2) return;
35
36 const [dark, light] = preset.colors.map((hex) => {
37 const { r, g, b } = colord(hex).toRgb();
38 return { r: r / 255, g: g / 255, b: b / 255 };
39 });
40
41 ['feFuncR', 'feFuncG', 'feFuncB'].forEach((func, i) => {
42 const ch = ['r', 'g', 'b'][i];
43 filter
44 .querySelector(func)
45 ?.setAttribute('tableValues', `${dark[ch]} ${light[ch]}`);
46 });
47 });
48 return cloned;
49 });
50 };
51
52 const generatePreviewHtml = (renderedHtml, styles) => {
53 const clone = document.documentElement.cloneNode(true);
54 const head = clone.querySelector('head');
55 const body = clone.querySelector('body');
56
57 // Strip all scripts
58 clone.querySelectorAll('script').forEach((el) => {
59 el.remove();
60 });
61
62 clone.querySelector('#block-style-variation-styles-inline-css')?.remove();
63 clone.querySelector('#admin-bar-inline-css')?.remove();
64
65 // Inject variation styles
66 const styleEl = head.appendChild(document.createElement('style'));
67 styleEl.textContent = [
68 styles?.colorAndFontsVariations ?? '',
69 styles?.vibes ?? '',
70 styles?.blockSupportsCss ?? '',
71 ].join('\n');
72
73 // Inject link styles
74 (styles?.linkStyles ?? []).forEach((href) => {
75 if (clone.querySelector(`link[href="${href}"]`)) return;
76
77 const link = document.createElement('link');
78
79 link.rel = 'stylesheet';
80 link.href = href;
81
82 head.appendChild(link);
83 });
84
85 const duotoneSvgNodes = getDuotoneSvgNodes(styles?.duotoneTheme);
86
87 // Set body to header + hero section, then append duotone SVGs
88 const headerHtml =
89 removeAnimationClasses(document.querySelector('header'))?.outerHTML ?? '';
90 body.innerHTML = `${headerHtml}${lowerImageQuality(renderedHtml)}`;
91 duotoneSvgNodes.forEach((node) => {
92 body.appendChild(node);
93 });
94
95 return `<!DOCTYPE html>${clone.outerHTML}`;
96 };
97
98 export const DesignOption = ({ renderedHtml, styles, isSelected, onClick }) => {
99 const iframeRef = useRef(null);
100 const { containerRef, scale, contentHeight, handleIframeLoad } =
101 useIframeScale();
102
103 const srcdoc = useMemo(
104 () => generatePreviewHtml(renderedHtml, styles),
105 [
106 renderedHtml,
107 styles?.linkStyles,
108 styles?.colorAndFontsVariations,
109 styles?.duotoneTheme,
110 styles?.vibes,
111 styles?.blockSupportsCss,
112 ],
113 );
114
115 return (
116 <button
117 ref={containerRef}
118 type="button"
119 style={{
120 height: `${(contentHeight ?? VIEWPORT_HEIGHT) * scale}px`,
121 }}
122 className={classnames(
123 'relative w-full cursor-pointer overflow-hidden rounded-md border shadow-md',
124 {
125 'border-design-main ring-wp ring-design-main': isSelected,
126 'border-gray-400': !isSelected,
127 },
128 )}
129 onClick={onClick}
130 onKeyDown={(e) => e.key === 'Enter' && onClick()}
131 >
132 <div
133 className="overflow-hidden"
134 style={{
135 width: VIEWPORT_WIDTH,
136 transform: `scale(${scale})`,
137 transformOrigin: 'top left',
138 }}
139 >
140 <iframe
141 ref={iframeRef}
142 title={__('Preview site design', 'extendify-local')}
143 onLoad={handleIframeLoad}
144 srcDoc={srcdoc}
145 style={{
146 width: '100%',
147 height: Math.max(contentHeight ?? VIEWPORT_HEIGHT, VIEWPORT_HEIGHT),
148 border: 0,
149 pointerEvents: 'none',
150 display: 'block',
151 overflow: 'hidden',
152 }}
153 />
154 </div>
155 </button>
156 );
157 };
158