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

163 lines 4.6 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 clone.querySelector('#admin-bar-css')?.remove();
62 clone.querySelector('#extendify-toolbar-reset')?.remove();
63 body.classList.remove('admin-bar');
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 body.removeAttribute('style');
89
90 const headerNode = removeAnimationClasses(document.querySelector('header'));
91 headerNode?.classList.remove('is-past-hero', 'is-scrolled');
92 headerNode?.removeAttribute('style');
93 const headerHtml = headerNode?.outerHTML ?? '';
94 body.innerHTML = `${headerHtml}<div class="entry-content">${lowerImageQuality(renderedHtml)}</div>`;
95 duotoneSvgNodes.forEach((node) => {
96 body.appendChild(node);
97 });
98
99 return `<!DOCTYPE html>${clone.outerHTML}`;
100 };
101
102 export const DesignOption = ({ renderedHtml, styles, isSelected, onClick }) => {
103 const { containerRef, scale, contentHeight, handleIframeLoad } =
104 useIframeScale({ viewportWidth: PREVIEW_VIEWPORT_WIDTH });
105
106 const srcdoc = useMemo(
107 () => generatePreviewHtml(renderedHtml, styles),
108 [
109 renderedHtml,
110 styles?.linkStyles,
111 styles?.colorAndFontsVariations,
112 styles?.duotoneTheme,
113 styles?.vibes,
114 styles?.blockSupportsCss,
115 ],
116 );
117
118 return (
119 <button
120 ref={containerRef}
121 type="button"
122 style={{
123 height: `${(contentHeight ?? PREVIEW_VIEWPORT_HEIGHT) * scale}px`,
124 }}
125 className={classnames(
126 'relative w-full cursor-pointer overflow-hidden rounded-md border shadow-md',
127 {
128 'border-design-main ring-wp ring-design-main': isSelected,
129 'border-gray-400': !isSelected,
130 },
131 )}
132 onClick={onClick}
133 onKeyDown={(e) => e.key === 'Enter' && onClick()}
134 >
135 <div
136 className="overflow-hidden"
137 style={{
138 width: PREVIEW_VIEWPORT_WIDTH,
139 transform: `scale(${scale})`,
140 transformOrigin: 'top left',
141 }}
142 >
143 <iframe
144 title={__('Preview site design', 'extendify-local')}
145 onLoad={handleIframeLoad}
146 srcDoc={srcdoc}
147 style={{
148 width: '100%',
149 height: Math.max(
150 contentHeight ?? PREVIEW_VIEWPORT_HEIGHT,
151 PREVIEW_VIEWPORT_HEIGHT,
152 ),
153 border: 0,
154 pointerEvents: 'none',
155 display: 'block',
156 overflow: 'hidden',
157 }}
158 />
159 </div>
160 </button>
161 );
162 };
163