PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / 2.2.0
GenerateBlocks v2.2.0
trunk 1.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.3.0
generateblocks / src / pattern-library / components / pattern.js
generateblocks / src / pattern-library / components Last commit date
add-library.js 2 years ago category-list.js 1 year ago insert-pattern.js 2 years ago library-cache.js 2 years ago library-layout.js 1 year ago library-provider.js 2 years ago library-selector.js 1 year ago manage-libraries.js 2 years ago pattern-details-header.js 1 year ago pattern-details.js 1 year ago pattern-list.js 1 year ago pattern-search.js 2 years ago pattern.js 1 year ago selected-patterns.js 1 year ago
pattern.js
268 lines
1 import { Spinner } from '@wordpress/components';
2 import { useEffect, useRef, useState, useLayoutEffect } from '@wordpress/element';
3 import imagesLoaded from 'imagesloaded';
4 import { useLibrary } from './library-provider';
5
6 export default function Pattern( { pattern, isLoading, isActive = false, globalStyleCSS } ) {
7 const {
8 id,
9 preview,
10 label,
11 scripts = [],
12 styles = [],
13 } = pattern;
14 const iframeRef = useRef();
15 const elementRef = useRef();
16 const [ height, setHeight ] = useState( 0 );
17 const [ injectContent, setInjectContent ] = useState( false );
18 const [ editorColors, setEditorColors ] = useState( {} );
19 const [ isLoaded, setIsLoaded ] = useState( false );
20 const [ patternWidth, setPatternWidth ] = useState( 0 );
21 const [ isResizing, setIsResizing ] = useState( false );
22 const { previewIframeWidth } = useLibrary();
23 const patternHeight = 350;
24 const viewport = patternWidth;
25 const iframe = 1280;
26 const editorStylesWrapper = document?.querySelector( '.editor-styles-wrapper' );
27
28 /**
29 * Debounce our resizing event.
30 * This is used to re-calculate the pattern width.
31 */
32 useEffect( () => {
33 let resizeTimer;
34
35 const handleResize = () => {
36 setIsResizing( true );
37 clearTimeout( resizeTimer );
38
39 resizeTimer = setTimeout( () => {
40 setIsResizing( false );
41 }, 500 );
42 };
43
44 window.addEventListener( 'resize', handleResize );
45
46 return () => {
47 window.removeEventListener( 'resize', handleResize );
48 };
49 }, [] );
50
51 /**
52 * Set the width of our patterns.
53 */
54 useEffect( () => {
55 if ( elementRef.current?.clientWidth && ! isResizing ) {
56 setPatternWidth( elementRef.current?.clientWidth );
57 }
58 }, [ elementRef.current?.clientWidth, isResizing ] );
59
60 /**
61 * Insert our pattern preview into the empty iframe.
62 */
63 useEffect( () => {
64 if ( ! injectContent ) {
65 return;
66 }
67
68 const document = iframeRef.current.contentWindow.document;
69
70 scripts.forEach( ( script ) => {
71 const scriptElement = document.createElement( 'script' );
72 scriptElement.defer = true;
73 scriptElement.src = script;
74 document.head.appendChild( scriptElement );
75 } );
76
77 styles.forEach( ( style ) => {
78 const styleElement = document.createElement( 'link' );
79 styleElement.rel = 'stylesheet';
80 styleElement.href = style;
81 document.head.appendChild( styleElement );
82 } );
83
84 document.body.innerHTML = preview;
85 document.head.innerHTML += '<style id="block-active"></style>';
86 document.head.innerHTML += '<style id="pattern-styles"></style>';
87
88 const globalStyleElement = document.createElement( 'style' );
89 globalStyleElement.innerHTML = globalStyleCSS;
90
91 const firstStyleElement = document.querySelector( 'head style' );
92 document.head.insertBefore( globalStyleElement, firstStyleElement );
93
94 imagesLoaded( document.body, () => {
95 setHeight( document.body.scrollHeight );
96 setIsLoaded( true );
97 } );
98 }, [ injectContent ] );
99
100 /**
101 * Store our editor background and text color.
102 */
103 useEffect( () => {
104 if ( ! editorStylesWrapper ) {
105 return;
106 }
107
108 const editorStyles = getComputedStyle( editorStylesWrapper );
109
110 if ( editorStyles ) {
111 setEditorColors( { background: editorStyles.backgroundColor, text: editorStyles.color } );
112 }
113 }, [ editorStylesWrapper?.style ] );
114
115 /**
116 * Mimic our editor styles in the pattern preview.
117 * This allows our patterns to have the same background/text colors as the editor.
118 */
119 useLayoutEffect( () => {
120 const document = iframeRef.current?.contentWindow?.document;
121
122 if ( document && document.querySelector && document.querySelector( '#pattern-styles' ) ) {
123 document.querySelector( '#pattern-styles' ).innerHTML = `body{background-color:${ editorColors?.background };color:${ editorColors?.text };}`;
124 }
125 }, [ editorColors, height ] );
126
127 /**
128 * Set the height of the preview iframe.
129 */
130 useEffect( () => {
131 if ( ! isActive ) {
132 return;
133 }
134
135 const document = iframeRef?.current?.contentWindow?.document;
136
137 imagesLoaded( document?.body, () => {
138 setHeight( document?.body?.scrollHeight );
139 } );
140 }, [ previewIframeWidth ] );
141
142 /**
143 * Add padding and center the pattern if it's not full width.
144 */
145 useEffect( () => {
146 const iframeDocument = iframeRef.current.contentWindow.document;
147 const iframeBody = iframeDocument.body;
148
149 if ( ! iframeBody ) {
150 return;
151 }
152
153 const elements = Array.from( iframeBody.querySelectorAll( '*' ) );
154
155 const firstVisibleElement = elements?.find( ( element ) => {
156 const { display } = getComputedStyle( element );
157 return display !== 'none';
158 } );
159
160 if ( firstVisibleElement ) {
161 const parentWidth = firstVisibleElement.parentElement.clientWidth;
162 const isFullWidth = firstVisibleElement.offsetWidth === parentWidth;
163
164 if ( ! isFullWidth ) {
165 iframeBody.style.padding = '100px';
166 firstVisibleElement.style.marginLeft = 'auto';
167 firstVisibleElement.style.marginRight = 'auto';
168 }
169 }
170 }, [ patternWidth, injectContent ] );
171
172 const viewportHeight = Math.round( height * ( viewport / iframe ) );
173
174 const wrapperStyle = {
175 opacity: isLoading ? 0 : 1,
176 height: patternHeight + 'px',
177 display: 'flex',
178 flexDirection: 'column',
179 justifyContent: ( viewportHeight + 40 ) < patternHeight ? 'center' : '',
180 };
181
182 const sandbox = [
183 'allow-same-origin',
184 ];
185
186 if ( isActive ) {
187 sandbox.push( 'allow-scripts' );
188 }
189
190 return (
191 <div
192 className="gb-pattern-frame"
193 style={ isActive ? {
194 backgroundColor: 'none',
195 padding: 0,
196 } : {} }
197 >
198 <div
199 ref={ elementRef }
200 className="gb-pattern"
201 style={ ! isActive ? wrapperStyle : { minHeight: '200px' } }
202 >
203 { ! isLoaded && <Spinner /> }
204 <div
205 style={ ! isActive ? {
206 width: `${ viewport }px`,
207 height: `${ viewportHeight }px`,
208 } : {} }
209 >
210 <div
211 style={ ! isActive ? {
212 height: height + 'px',
213 width: `${ ( ( iframe / viewport ) * 100 ) }%`,
214 transformOrigin: '0 0',
215 transform: `scale( ${ viewport / iframe } )`,
216 } : {} }
217 >
218 <iframe
219 id={ id }
220 onLoad={ () => {
221 setInjectContent( true );
222
223 const iframeDoc = iframeRef.current.contentDocument || iframeRef.current.contentWindow.document;
224
225 iframeDoc.addEventListener( 'click', ( event ) => {
226 const clickedElement = event.target;
227
228 if ( 'A' === clickedElement.tagName ) {
229 const href = clickedElement.getAttribute( 'href' );
230
231 if ( href && ! href.startsWith( '#' ) ) {
232 event.preventDefault();
233 event.stopPropagation();
234 }
235 }
236
237 // Reset our height when we click anything in our preview.
238 // This accounts for height changes from accordions etc...
239 if ( isActive ) {
240 setTimeout( () => {
241 setHeight( iframeDoc.body.scrollHeight );
242 }, 500 );
243 }
244 } );
245 } }
246 title={ label }
247 src={ generateBlocksPatternLibrary.patternPreviewUrl }
248 ref={ iframeRef }
249 style={ {
250 height: height + 'px',
251 border: '0',
252 pointerEvents: ! isActive ? 'none' : '',
253 width: isActive ? previewIframeWidth : `${ iframe }px`,
254 opacity: ! isLoaded ? 0 : 1,
255 display: ( isActive && '100%' !== previewIframeWidth ) ? 'block' : '',
256 margin: isActive && '100%' !== previewIframeWidth ? '0 auto' : '',
257 } }
258 tabIndex="-1"
259 loading="lazy"
260 sandbox={ sandbox.join( ' ' ) }
261 />
262 </div>
263 </div>
264 </div>
265 </div>
266 );
267 }
268