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

167 lines 4.7 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('#extendify-palette-preview-css')?.remove();
61 clone.querySelector('#admin-bar-inline-css')?.remove();
62 clone.querySelector('#admin-bar-css')?.remove();
63 clone.querySelector('#extendify-toolbar-reset')?.remove();
64 body.classList.remove('admin-bar');
65
66 // Inject variation styles
67 const styleEl = head.appendChild(document.createElement('style'));
68 styleEl.textContent = [
69 styles?.colorAndFontsVariations ?? '',
70 // Ties the option on specificity, so it only wins by coming after it.
71 styles?.paletteCss ?? '',
72 styles?.vibes ?? '',
73 styles?.blockSupportsCss ?? '',
74 ].join('\n');
75
76 // Inject link styles
77 (styles?.linkStyles ?? []).forEach((href) => {
78 if (clone.querySelector(`link[href="${href}"]`)) return;
79
80 const link = document.createElement('link');
81
82 link.rel = 'stylesheet';
83 link.href = href;
84
85 head.appendChild(link);
86 });
87
88 const duotoneSvgNodes = getDuotoneSvgNodes(styles?.duotoneTheme);
89
90 // Set body to header + hero section, then append duotone SVGs
91 body.removeAttribute('style');
92
93 const headerNode = removeAnimationClasses(document.querySelector('header'));
94 headerNode?.classList.remove('is-past-hero', 'is-scrolled');
95 headerNode?.removeAttribute('style');
96 const headerHtml = headerNode?.outerHTML ?? '';
97 body.innerHTML = `${headerHtml}<div class="entry-content">${lowerImageQuality(renderedHtml)}</div>`;
98 duotoneSvgNodes.forEach((node) => {
99 body.appendChild(node);
100 });
101
102 return `<!DOCTYPE html>${clone.outerHTML}`;
103 };
104
105 export const DesignOption = ({ renderedHtml, styles, isSelected, onClick }) => {
106 const { containerRef, scale, contentHeight, handleIframeLoad } =
107 useIframeScale({ viewportWidth: PREVIEW_VIEWPORT_WIDTH });
108
109 const srcdoc = useMemo(
110 () => generatePreviewHtml(renderedHtml, styles),
111 [
112 renderedHtml,
113 styles?.linkStyles,
114 styles?.colorAndFontsVariations,
115 styles?.paletteCss,
116 styles?.duotoneTheme,
117 styles?.vibes,
118 styles?.blockSupportsCss,
119 ],
120 );
121
122 return (
123 <button
124 ref={containerRef}
125 type="button"
126 style={{
127 height: `${(contentHeight ?? PREVIEW_VIEWPORT_HEIGHT) * scale}px`,
128 }}
129 className={classnames(
130 'relative w-full cursor-pointer overflow-hidden rounded-md border shadow-md',
131 {
132 'border-design-main ring-wp ring-design-main': isSelected,
133 'border-gray-400': !isSelected,
134 },
135 )}
136 onClick={onClick}
137 onKeyDown={(e) => e.key === 'Enter' && onClick()}
138 >
139 <div
140 className="overflow-hidden"
141 style={{
142 width: PREVIEW_VIEWPORT_WIDTH,
143 transform: `scale(${scale})`,
144 transformOrigin: 'top left',
145 }}
146 >
147 <iframe
148 title={__('Preview site design', 'extendify-local')}
149 onLoad={handleIframeLoad}
150 srcDoc={srcdoc}
151 style={{
152 width: '100%',
153 height: Math.max(
154 contentHeight ?? PREVIEW_VIEWPORT_HEIGHT,
155 PREVIEW_VIEWPORT_HEIGHT,
156 ),
157 border: 0,
158 pointerEvents: 'none',
159 display: 'block',
160 overflow: 'hidden',
161 }}
162 />
163 </div>
164 </button>
165 );
166 };
167