| 1 |
import { useEffect, useRef, useState } from '@wordpress/element'; |
| 2 |
import { __ } from '@wordpress/i18n'; |
| 3 |
import { Lang, Theme } from 'shiki'; |
| 4 |
import { useTheme } from '../../hooks/useTheme'; |
| 5 |
|
| 6 |
type ThemePreviewProps = { |
| 7 |
id: string; |
| 8 |
theme: Theme; |
| 9 |
lang: Lang; |
| 10 |
code: string; |
| 11 |
fontSize: string; |
| 12 |
lineHeight: string; |
| 13 |
fontFamily: string; |
| 14 |
onClick: () => void; |
| 15 |
}; |
| 16 |
export const ThemePreview = ({ |
| 17 |
id, |
| 18 |
theme, |
| 19 |
lang, |
| 20 |
onClick, |
| 21 |
code, |
| 22 |
fontSize, |
| 23 |
lineHeight, |
| 24 |
fontFamily, |
| 25 |
}: ThemePreviewProps) => { |
| 26 |
const [inView, setInView] = useState(false); |
| 27 |
const { highlighter, error, loading } = useTheme({ |
| 28 |
theme, |
| 29 |
// If no code is written yet, show a classic Carmack snippet |
| 30 |
lang: code ? lang : 'c', |
| 31 |
ready: inView, |
| 32 |
}); |
| 33 |
const [codeRendered, setCode] = useState(''); |
| 34 |
const [backgroundColor, setBg] = useState('#ffffff'); |
| 35 |
const observer = useRef<IntersectionObserver>( |
| 36 |
new IntersectionObserver(([entry]) => { |
| 37 |
if (entry.isIntersecting) { |
| 38 |
setInView(true); |
| 39 |
observer.current.disconnect(); |
| 40 |
} |
| 41 |
}), |
| 42 |
); |
| 43 |
const codeSnippet = ` |
| 44 |
float Q_rsqrt( float number ) |
| 45 |
{ |
| 46 |
long i; |
| 47 |
float x2, y; |
| 48 |
const float threehalfs = 1.5F; |
| 49 |
|
| 50 |
x2 = number * 0.5F; |
| 51 |
y = number; |
| 52 |
i = * ( long * ) &y; // evil floating point bit level hacking |
| 53 |
i = 0x5f3759df - ( i >> 1 ); // what the frack? |
| 54 |
y = * ( float * ) &i; |
| 55 |
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration |
| 56 |
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed |
| 57 |
|
| 58 |
return y; |
| 59 |
}`.trim(); |
| 60 |
|
| 61 |
useEffect(() => { |
| 62 |
if (!highlighter) return; |
| 63 |
const hl = code |
| 64 |
? highlighter.codeToHtml(code, { lang }) |
| 65 |
: highlighter.codeToHtml(codeSnippet, { lang: 'c' }); |
| 66 |
setCode(hl); |
| 67 |
setBg(highlighter.getBackgroundColor()); |
| 68 |
}, [highlighter, lang, code, codeSnippet]); |
| 69 |
|
| 70 |
return ( |
| 71 |
<button |
| 72 |
id={id} |
| 73 |
type="button" |
| 74 |
onClick={onClick} |
| 75 |
className="p-4 px-3 border flex items-start w-full text-left outline-none cursor-pointer no-underline ring-offset-2 ring-offset-white focus:shadow-none focus:ring-wp overflow-x-auto" |
| 76 |
style={{ backgroundColor, minHeight: '50px' }}> |
| 77 |
{loading || error || !inView ? ( |
| 78 |
<span |
| 79 |
id={id} |
| 80 |
ref={(el) => { |
| 81 |
if (!el) return; |
| 82 |
observer.current.observe(el); |
| 83 |
}} |
| 84 |
style={{ minHeight: '200px' }} |
| 85 |
className="flex items-center justify-center p-6 w-full"> |
| 86 |
{error?.message || __('Loading...', 'code-block-pro')} |
| 87 |
</span> |
| 88 |
) : ( |
| 89 |
<span |
| 90 |
className="pointer-events-none" |
| 91 |
style={{ |
| 92 |
fontSize: fontSize, |
| 93 |
// Tiny check to avoid block invalidation error |
| 94 |
fontFamily: fontFamily, |
| 95 |
lineHeight: lineHeight, |
| 96 |
}} |
| 97 |
dangerouslySetInnerHTML={{ __html: codeRendered }} |
| 98 |
/> |
| 99 |
)} |
| 100 |
</button> |
| 101 |
); |
| 102 |
}; |
| 103 |
|