PluginProbe
Extendify / 3.0.6
Extendify v3.0.6
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.6, at src/Agent/workflows/theme/components/change-site-design/DesignOption.jsx

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