PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / 1.9.0
GenerateBlocks v1.9.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 2 years ago insert-pattern.js 2 years ago library-cache.js 2 years ago library-layout.js 2 years ago library-provider.js 2 years ago library-selector.js 2 years ago manage-libraries.js 2 years ago pattern-details-header.js 2 years ago pattern-details.js 2 years ago pattern-list.js 2 years ago pattern-search.js 2 years ago pattern.js 2 years ago selected-patterns.js 2 years ago
pattern.js
283 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 } = pattern;
13 const iframeRef = useRef();
14 const elementRef = useRef();
15 const [ height, setHeight ] = useState( 0 );
16 const [ injectContent, setInjectContent ] = useState( false );
17 const [ editorColors, setEditorColors ] = useState( {} );
18 const [ isVisible, setIsVisible ] = useState( !! isActive );
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 * Set up lazy loading on the iframes.
62 */
63 useEffect( () => {
64 const intersectionObserver = new IntersectionObserver(
65 ( entries ) => {
66 entries.forEach( ( entry ) => {
67 if ( entry.isIntersecting ) {
68 setIsVisible( true );
69 intersectionObserver.disconnect();
70 }
71 } );
72 },
73 {
74 root: null,
75 rootMargin: '0px',
76 threshold: 0.01,
77 }
78 );
79
80 if ( elementRef.current ) {
81 intersectionObserver.observe( elementRef.current );
82 }
83
84 return () => {
85 if ( intersectionObserver ) {
86 intersectionObserver.disconnect();
87 }
88 };
89 }, [] );
90
91 /**
92 * Insert our pattern preview into the empty iframe.
93 */
94 useEffect( () => {
95 if ( ! isVisible || ! injectContent ) {
96 return;
97 }
98
99 const document = iframeRef.current.contentWindow.document;
100
101 scripts.forEach( ( script ) => {
102 const scriptElement = document.createElement( 'script' );
103 scriptElement.defer = true;
104 scriptElement.src = script;
105 document.head.appendChild( scriptElement );
106 } );
107
108 document.body.innerHTML = preview;
109 document.head.innerHTML += '<style id="block-active"></style>';
110 document.head.innerHTML += '<style id="pattern-styles"></style>';
111
112 const globalStyleElement = document.createElement( 'style' );
113 globalStyleElement.innerHTML = globalStyleCSS;
114
115 const firstStyleElement = document.querySelector( 'head style' );
116 document.head.insertBefore( globalStyleElement, firstStyleElement );
117
118 imagesLoaded( document.body, () => {
119 setHeight( document.body.scrollHeight );
120 setIsLoaded( true );
121 } );
122 }, [ injectContent, isVisible ] );
123
124 /**
125 * Store our editor background and text color.
126 */
127 useEffect( () => {
128 if ( ! editorStylesWrapper ) {
129 return;
130 }
131
132 const styles = getComputedStyle( editorStylesWrapper );
133
134 if ( styles ) {
135 setEditorColors( { background: styles.backgroundColor, text: styles.color } );
136 }
137 }, [ editorStylesWrapper?.style ] );
138
139 /**
140 * Mimic our editor styles in the pattern preview.
141 * This allows our patterns to have the same background/text colors as the editor.
142 */
143 useLayoutEffect( () => {
144 const document = iframeRef.current?.contentWindow?.document;
145
146 if ( document && document.querySelector && document.querySelector( '#pattern-styles' ) ) {
147 document.querySelector( '#pattern-styles' ).innerHTML = `body{background-color:${ editorColors?.background };color:${ editorColors?.text };}`;
148 }
149 }, [ editorColors, height ] );
150
151 /**
152 * Set the height of the preview iframe.
153 */
154 useEffect( () => {
155 if ( ! isActive ) {
156 return;
157 }
158
159 const document = iframeRef?.current?.contentWindow?.document;
160
161 imagesLoaded( document?.body, () => {
162 setHeight( document?.body?.scrollHeight );
163 } );
164 }, [ previewIframeWidth ] );
165
166 /**
167 * Add padding and center the pattern if it's not full width.
168 */
169 useEffect( () => {
170 const iframeDocument = iframeRef.current.contentWindow.document;
171 const iframeBody = iframeDocument.body;
172
173 if ( ! iframeBody ) {
174 return;
175 }
176
177 const elements = Array.from( iframeBody.querySelectorAll( '*' ) );
178
179 const firstVisibleElement = elements?.find( ( element ) => {
180 const { display } = getComputedStyle( element );
181 return display !== 'none';
182 } );
183
184 if ( firstVisibleElement ) {
185 const parentWidth = firstVisibleElement.parentElement.clientWidth;
186 const isFullWidth = firstVisibleElement.offsetWidth === parentWidth;
187
188 if ( ! isFullWidth ) {
189 iframeBody.style.padding = '100px';
190 firstVisibleElement.style.marginLeft = 'auto';
191 firstVisibleElement.style.marginRight = 'auto';
192 }
193 }
194 }, [ patternWidth, injectContent, isVisible ] );
195
196 const viewportHeight = Math.round( height * ( viewport / iframe ) );
197
198 const wrapperStyle = {
199 opacity: isLoading ? 0 : 1,
200 height: patternHeight + 'px',
201 display: 'flex',
202 flexDirection: 'column',
203 justifyContent: ( viewportHeight + 40 ) < patternHeight ? 'center' : '',
204 };
205
206 return (
207 <div
208 className="gb-pattern-frame"
209 style={ isActive ? {
210 backgroundColor: 'none',
211 padding: 0,
212 } : {} }
213 >
214 <div
215 ref={ elementRef }
216 className="gb-pattern"
217 style={ ! isActive ? wrapperStyle : { minHeight: '200px' } }
218 >
219 { !! isVisible && ! isLoaded && <Spinner /> }
220 <div
221 style={ ! isActive ? {
222 width: `${ viewport }px`,
223 height: `${ viewportHeight }px`,
224 } : {} }
225 >
226 <div
227 style={ ! isActive ? {
228 height: height + 'px',
229 width: `${ ( ( iframe / viewport ) * 100 ) }%`,
230 transformOrigin: '0 0',
231 transform: `scale( ${ viewport / iframe } )`,
232 } : {} }
233 >
234 <iframe
235 id={ id }
236 onLoad={ () => {
237 if ( isVisible ) {
238 setInjectContent( true );
239 }
240
241 const iframeDoc = iframeRef.current.contentDocument || iframeRef.current.contentWindow.document;
242
243 iframeDoc.addEventListener( 'click', ( event ) => {
244 const clickedElement = event.target;
245
246 if ( 'A' === clickedElement.tagName ) {
247 const href = clickedElement.getAttribute( 'href' );
248
249 if ( href && ! href.startsWith( '#' ) ) {
250 event.preventDefault();
251 event.stopPropagation();
252 }
253 }
254
255 // Reset our height when we click anything in our preview.
256 // This accounts for height changes from accordions etc...
257 if ( isActive ) {
258 setHeight( iframeDoc.body.scrollHeight );
259 }
260 } );
261 } }
262 title={ label }
263 src={ generateBlocksPatternLibrary.patternPreviewUrl }
264 ref={ iframeRef }
265 style={ {
266 height: height + 'px',
267 border: '0',
268 pointerEvents: ! isActive ? 'none' : '',
269 width: isActive ? previewIframeWidth : `${ iframe }px`,
270 opacity: ! isLoaded ? 0 : 1,
271 display: ( isActive && '100%' !== previewIframeWidth ) ? 'block' : '',
272 margin: isActive && '100%' !== previewIframeWidth ? '0 auto' : '',
273 } }
274 tabIndex="-1"
275 loading="lazy"
276 />
277 </div>
278 </div>
279 </div>
280 </div>
281 );
282 }
283