/home/smartbloks/.trash/extendify/src/Onboarding/components/SmallPreview.js
import { BlockPreview, transformStyles } from '@wordpress/block-editor'
import { rawHandler } from '@wordpress/blocks'
import { useState, useRef, useEffect, useMemo } from '@wordpress/element'
import { __ } from '@wordpress/i18n'
import { colord } from 'colord'
import { AnimatePresence, motion } from 'framer-motion'
import { parseThemeJson } from '@onboarding/api/WPApi'
import { useFetch } from '@onboarding/hooks/useFetch'
import { useIsMounted } from '@onboarding/hooks/useIsMounted'
import { lowerImageQuality } from '@onboarding/lib/util'
import { useUserSelectionStore } from '@onboarding/state/UserSelections'
const fetcher = async (themeJson) => {
if (!themeJson) return '{}'
const res = await parseThemeJson(JSON.stringify(themeJson))
if (!res?.styles) {
throw new Error('Invalid theme json')
}
return { data: res.styles }
}
const styleOverrides = `
.wp-block-cover {
min-height: 100% !important;
height: calc(100vh - 0px);
max-height: 905px;
}
`
export const SmallPreview = ({ style, onSelect }) => {
const { siteType } = useUserSelectionStore()
const isMounted = useIsMounted()
const [code, setCode] = useState('')
const [loaded, setLoaded] = useState(false)
const [waitForIframe, setWaitForIframe] = useState(0)
const [iFrame, setIFrame] = useState(null)
const [inView, setInView] = useState(false)
const previewContainer = useRef(null)
const blockRef = useRef(null)
const observer = useRef(null)
const variation = style?.variation
const { data: themeJson } = useFetch(
inView && variation ? variation : null,
fetcher,
)
const theme = variation?.settings?.color?.palette?.theme
const blocks = useMemo(
() => rawHandler({ HTML: lowerImageQuality(code) }),
[code],
)
const transformedStyles = useMemo(
() =>
themeJson
? transformStyles(
[{ css: themeJson }],
'.editor-styles-wrapper',
)
: null,
[themeJson],
)
useEffect(() => {
if (iFrame || !inView) return
// continuously check for iframe
const interval = setTimeout(() => {
const container = previewContainer.current
const frame = container?.querySelector('iframe[title]')
if (!frame) return setWaitForIframe((prev) => prev + 1)
setIFrame(frame)
}, 100)
return () => clearTimeout(interval)
}, [iFrame, inView, waitForIframe])
useEffect(() => {
if (!themeJson || !style?.code) return
const code = [style?.headerCode, style?.code, style?.footerCode]
.filter(Boolean)
.join('')
.replace(
// <!-- wp:navigation --> <!-- /wp:navigation -->
/<!-- wp:navigation[.\S\s]*?\/wp:navigation -->/g,
'<!-- wp:paragraph {"className":"tmp-nav"} --><p class="tmp-nav">Home | About | Contact</p ><!-- /wp:paragraph -->',
)
.replace(
// <!-- wp:navigation /-->
/<!-- wp:navigation.*\/-->/g,
'<!-- wp:paragraph {"className":"tmp-nav"} --><p class="tmp-nav">Home | About | Contact</p ><!-- /wp:paragraph -->',
)
.replace(
/<!-- wp:site-logo.*\/-->/g,
'<!-- wp:paragraph {"className":"custom-logo"} --><img class="custom-logo" style="height: 40px;" src="https://assets.extendify.com/demo-content/logos/extendify-demo-logo.png"><!-- /wp:paragraph -->',
)
setCode(code)
}, [siteType?.slug, themeJson, style])
useEffect(() => {
if (!blocks?.length || !iFrame) return
let timer, timer2
// Inserts theme styles after iframe is loaded
const load = () => {
const doc = iFrame.contentDocument
// Remove load-styles in case WP laods them
doc?.querySelector('[href*=load-styles]')?.remove()
const style = `<style id="ext-tj">${transformedStyles}${styleOverrides}</style>`
if (!doc?.getElementById('ext-tj')) {
doc?.head?.insertAdjacentHTML('beforeend', style)
}
timer2 = setTimeout(() => isMounted.current && setLoaded(true), 100)
clearTimeout(timer)
}
iFrame.addEventListener('load', load)
// In some cases, the load event doesn't fire.
timer = setTimeout(load, 2000)
return () => {
iFrame?.removeEventListener('load', load)
;[(timer, timer2)].forEach((t) => clearTimeout(t))
}
}, [blocks, transformedStyles, isMounted, inView, iFrame])
useEffect(() => {
if (observer.current) return
observer.current = new IntersectionObserver((entries) => {
entries[0].isIntersecting && setInView(true)
})
observer.current.observe(blockRef.current)
return () => observer.current.disconnect()
}, [])
return (
<>
<div
data-test="layout-preview"
className="w-full h-full relative overflow-hidden"
ref={blockRef}
role={onSelect ? 'button' : undefined}
tabIndex={onSelect ? 0 : undefined}
aria-label={
onSelect ? __('Press to select', 'extendify') : undefined
}
onKeyDown={(e) => {
if (['Enter', 'Space', ' '].includes(e.key)) {
onSelect && onSelect({ ...style, variation })
}
}}
onClick={
onSelect
? () => onSelect({ ...style, variation })
: () => {}
}>
{inView ? (
<motion.div
ref={previewContainer}
className="absolute inset-0 z-20"
initial={{ opacity: 0 }}
animate={{ opacity: loaded ? 1 : 0 }}>
<BlockPreview blocks={blocks} viewportWidth={1400} />
</motion.div>
) : null}
<AnimatePresence>
{loaded || (
<motion.div
initial={{ opacity: 0.7 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
className="absolute inset-0 z-30"
style={{
backgroundColor: colord(
theme?.find(
({ slug }) => slug === 'primary',
)?.color ?? '#ccc',
)
.alpha(0.25)
.toRgbString(),
backgroundImage:
'linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0) 100%)',
backgroundSize: '600% 600%',
animation:
'extendify-loading-skeleton 10s ease-in-out infinite',
}}
/>
)}
</AnimatePresence>
</div>
</>
)
}