| 1 |
import { useIsMounted } from '@launch/hooks/useIsMounted'; |
| 2 |
import { |
| 3 |
useCallback, |
| 4 |
useLayoutEffect, |
| 5 |
useRef, |
| 6 |
useState, |
| 7 |
} from '@wordpress/element'; |
| 8 |
import { debounce } from 'lodash'; |
| 9 |
|
| 10 |
const originalHeights = new WeakMap(); |
| 11 |
|
| 12 |
export const usePreviewIframe = ({ container, onLoad, ready, loadDelay }) => { |
| 13 |
const isMounted = useIsMounted(); |
| 14 |
const [waitForIframe, setWaitForIframe] = useState(0); |
| 15 |
const [iFrame, setIFrame] = useState(null); |
| 16 |
const [maybeOk, setMaybeOk] = useState(false); |
| 17 |
const isUpdating = useRef(false); |
| 18 |
|
| 19 |
const updateCoverBlocks = useCallback((frame, cntnr) => { |
| 20 |
if (!frame) return; |
| 21 |
|
| 22 |
const ft = frame.getBoundingClientRect().top; |
| 23 |
const ct = cntnr.getBoundingClientRect().top; |
| 24 |
// If they have scrolled, don't mess with it |
| 25 |
if (ft < ct) return; |
| 26 |
|
| 27 |
isUpdating.current = true; |
| 28 |
|
| 29 |
// WP scales the iframe to fit the container |
| 30 |
// Get the scale value from the transform property |
| 31 |
let scale = cntnr |
| 32 |
.querySelector('[style*="scale"]') |
| 33 |
?.style?.transform?.match(/scale\((.*?)\)/)?.[1]; |
| 34 |
scale = scale ? parseFloat(scale) : null; |
| 35 |
// Get the height of the container and scale it |
| 36 |
const cntnrHScaled = cntnr.offsetHeight / (scale ?? 1); |
| 37 |
frame.style.setProperty('max-height', `${cntnrHScaled}px`, 'important'); |
| 38 |
|
| 39 |
const coverBlocks = |
| 40 |
frame?.contentDocument?.querySelectorAll('.wp-block-cover'); |
| 41 |
for (const el of coverBlocks) { |
| 42 |
if (!originalHeights.has(el)) { |
| 43 |
// Cache the original 'vh' value |
| 44 |
originalHeights.set(el, el.style.minHeight); |
| 45 |
} |
| 46 |
// Reapply the original 'vh' value so it can be used in computations |
| 47 |
el.style.minHeight = originalHeights.get(el); |
| 48 |
} |
| 49 |
|
| 50 |
for (const el of coverBlocks) { |
| 51 |
if (!frame.contentDocument?.defaultView) return; |
| 52 |
// Get the computed height in px and use it for your calculation |
| 53 |
const computedHeight = parseFloat( |
| 54 |
frame.contentDocument.defaultView.getComputedStyle(el).height, |
| 55 |
); |
| 56 |
el.offsetHeight; // Force a reflow |
| 57 |
el.style.minHeight = |
| 58 |
computedHeight > 784 ? '784px' : `${computedHeight}px`; |
| 59 |
} |
| 60 |
|
| 61 |
frame.style.setProperty('max-height', 'none', 'important'); |
| 62 |
isUpdating.current = false; |
| 63 |
}, []); |
| 64 |
|
| 65 |
useLayoutEffect(() => { |
| 66 |
if (!ready) return; |
| 67 |
// continuously check for iframe |
| 68 |
const interval = setTimeout(() => { |
| 69 |
const frame = container?.querySelector('iframe[title]'); |
| 70 |
// If not found, retry by updating state |
| 71 |
if (!frame) return setWaitForIframe((prev) => prev + 1); |
| 72 |
setIFrame(frame); |
| 73 |
requestAnimationFrame(() => onLoad(frame, container)); |
| 74 |
}, 100); |
| 75 |
return () => clearTimeout(interval); |
| 76 |
}, [iFrame, ready, waitForIframe, container, onLoad]); |
| 77 |
|
| 78 |
useLayoutEffect(() => { |
| 79 |
setMaybeOk(false); |
| 80 |
// After the iFrame is found, wait for it to load |
| 81 |
// Note: using load event is not reliable |
| 82 |
if (!iFrame?.contentDocument) return; |
| 83 |
|
| 84 |
const observerConfig = { |
| 85 |
attributes: false, |
| 86 |
childList: true, |
| 87 |
subtree: true, |
| 88 |
}; |
| 89 |
|
| 90 |
const loaded = debounce(() => { |
| 91 |
if (!isMounted.current || isUpdating.current) return; |
| 92 |
// Allow the skeleton loader to be removed |
| 93 |
setTimeout(() => setMaybeOk(true), loadDelay); |
| 94 |
updateCoverBlocks(iFrame, container); |
| 95 |
}, 50); |
| 96 |
|
| 97 |
// Run once in case the iframe is already loaded |
| 98 |
const firstRun = requestAnimationFrame(() => loaded(iFrame, container)); |
| 99 |
// Allow only for a max 2s wait time in case the mutation observer gets stuck |
| 100 |
const longestWait = setTimeout(() => { |
| 101 |
if (isMounted.current) { |
| 102 |
m.disconnect(); |
| 103 |
loaded(); |
| 104 |
} |
| 105 |
}, 2000); |
| 106 |
// Continuously check for changes |
| 107 |
const m = new MutationObserver(loaded); |
| 108 |
m.observe(iFrame.contentDocument, observerConfig); |
| 109 |
|
| 110 |
return () => { |
| 111 |
cancelAnimationFrame(firstRun); |
| 112 |
clearTimeout(longestWait); |
| 113 |
loaded.cancel(); |
| 114 |
m?.disconnect(); |
| 115 |
}; |
| 116 |
}, [iFrame, container, isMounted, ready, updateCoverBlocks, loadDelay]); |
| 117 |
|
| 118 |
return { loading: !iFrame, ready: maybeOk }; |
| 119 |
}; |
| 120 |
|