256 lines
7.8 KiB
TypeScript
256 lines
7.8 KiB
TypeScript
import { ChangeEvent } from 'react'
|
||
import '@/components/Playground/Output/Preview/render.scss'
|
||
import { COLOR_FONT_MAIN } from '@/constants/common.constants'
|
||
import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw'
|
||
import HideScrollbar from '@/components/common/HideScrollbar'
|
||
|
||
interface RenderProps {
|
||
iframeKey: string
|
||
compiledCode: string
|
||
mobileMode?: boolean
|
||
}
|
||
|
||
interface IMessage {
|
||
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE' | 'SCALE'
|
||
msg: string
|
||
data: {
|
||
compiledCode?: string
|
||
zoom?: number
|
||
}
|
||
}
|
||
|
||
interface IDevice {
|
||
name: string
|
||
width: number
|
||
height: number
|
||
}
|
||
|
||
const getIframeUrl = (iframeRaw: string) => {
|
||
const shimsUrl = '//unpkg.com/es-module-shims/dist/es-module-shims.js'
|
||
// 判断浏览器是否支持esm ,不支持esm就引入es-module-shims
|
||
const newIframeRaw =
|
||
typeof import.meta === 'undefined'
|
||
? iframeRaw.replace(
|
||
'<!-- es-module-shims -->',
|
||
`<script async src="${shimsUrl}"></script>`
|
||
)
|
||
: iframeRaw
|
||
return URL.createObjectURL(new Blob([newIframeRaw], { type: 'text/html' }))
|
||
}
|
||
|
||
const iframeUrl = getIframeUrl(iframeRaw)
|
||
|
||
const Render = ({ iframeKey, compiledCode, mobileMode = false }: RenderProps) => {
|
||
const iframeRef = useRef<HTMLIFrameElement>(null)
|
||
const [isLoaded, setIsLoaded] = useState(false)
|
||
const [selectedDevice, setSelectedDevice] = useState('Pixel 7')
|
||
const [zoom, setZoom] = useState(1)
|
||
const [isRotate, setIsRotate] = useState(false)
|
||
|
||
const devices: IDevice[] = [
|
||
{
|
||
name: 'iPhone SE',
|
||
width: 375,
|
||
height: 667
|
||
},
|
||
{
|
||
name: 'iPhone XR',
|
||
width: 414,
|
||
height: 896
|
||
},
|
||
{
|
||
name: 'iPhone 12 Pro',
|
||
width: 390,
|
||
height: 844
|
||
},
|
||
{
|
||
name: 'iPhone 14 Pro Max',
|
||
width: 430,
|
||
height: 932
|
||
},
|
||
{
|
||
name: 'Pixel 7',
|
||
width: 412,
|
||
height: 915
|
||
},
|
||
{
|
||
name: 'Samsung Galaxy S8+',
|
||
width: 360,
|
||
height: 740
|
||
},
|
||
{
|
||
name: 'Samsung Galaxy S20 Ultra',
|
||
width: 412,
|
||
height: 915
|
||
},
|
||
{
|
||
name: 'iPad Mini',
|
||
width: 768,
|
||
height: 1024
|
||
},
|
||
{
|
||
name: 'iPad Air',
|
||
width: 820,
|
||
height: 1180
|
||
},
|
||
{
|
||
name: 'iPad Pro',
|
||
width: 1024,
|
||
height: 1366
|
||
},
|
||
{
|
||
name: 'Surface Pro 7',
|
||
width: 912,
|
||
height: 1368
|
||
},
|
||
{
|
||
name: 'Surface Duo',
|
||
width: 540,
|
||
height: 720
|
||
},
|
||
{
|
||
name: 'Galaxy Fold',
|
||
width: 280,
|
||
height: 653
|
||
},
|
||
{
|
||
name: 'Asus Zenbook Fold',
|
||
width: 853,
|
||
height: 1280
|
||
},
|
||
{
|
||
name: 'Samsung Galaxy A51/71',
|
||
width: 412,
|
||
height: 914
|
||
},
|
||
{
|
||
name: 'Nest Hub',
|
||
width: 1024,
|
||
height: 600
|
||
},
|
||
{
|
||
name: 'Nest Hub Max',
|
||
width: 1280,
|
||
height: 800
|
||
}
|
||
]
|
||
|
||
const handleOnChangeDevice = (e: ChangeEvent<HTMLSelectElement>) => {
|
||
setSelectedDevice(e.target.value)
|
||
}
|
||
|
||
const handleOnChangeZoom = (e: ChangeEvent<HTMLInputElement>) => {
|
||
setZoom(Number(e.target.value))
|
||
}
|
||
|
||
const handleOnRotateDevice = () => {
|
||
setIsRotate(!isRotate)
|
||
}
|
||
|
||
useEffect(() => {
|
||
if (isLoaded) {
|
||
iframeRef.current?.contentWindow?.postMessage(
|
||
{
|
||
type: 'UPDATE',
|
||
data: { compiledCode }
|
||
} as IMessage,
|
||
'*'
|
||
)
|
||
}
|
||
}, [isLoaded, compiledCode])
|
||
|
||
useEffect(() => {
|
||
if (isLoaded) {
|
||
iframeRef.current?.contentWindow?.postMessage(
|
||
{
|
||
type: 'SCALE',
|
||
data: { zoom: zoom }
|
||
} as IMessage,
|
||
'*'
|
||
)
|
||
}
|
||
}, [isLoaded, zoom])
|
||
|
||
return mobileMode ? (
|
||
<>
|
||
<HideScrollbar
|
||
className={'mobile-mode-background'}
|
||
isShowVerticalScrollbar
|
||
isShowHorizontalScrollbar
|
||
autoHideWaitingTime={1000}
|
||
>
|
||
<div className={'mobile-mode-content'} style={{ zoom }}>
|
||
<div className={`device${isRotate ? ' rotate' : ''}`}>
|
||
<div className={`device-header${isRotate ? ' rotate' : ''}`} />
|
||
<div
|
||
className={`device-content${isRotate ? ' rotate' : ''}`}
|
||
style={{
|
||
width: isRotate
|
||
? (devices.find((value) => value.name === selectedDevice)
|
||
?.height ?? 915)
|
||
: (devices.find((value) => value.name === selectedDevice)
|
||
?.width ?? 412),
|
||
height: isRotate
|
||
? (devices.find((value) => value.name === selectedDevice)
|
||
?.width ?? 412)
|
||
: (devices.find((value) => value.name === selectedDevice)
|
||
?.height ?? 915)
|
||
}}
|
||
>
|
||
<iframe
|
||
data-component={'playground-output-preview-render'}
|
||
key={iframeKey}
|
||
ref={iframeRef}
|
||
src={iframeUrl}
|
||
onLoad={() => setIsLoaded(true)}
|
||
sandbox="allow-downloads allow-forms allow-modals allow-scripts"
|
||
allow={'clipboard-read; clipboard-write'}
|
||
/>
|
||
</div>
|
||
<div className={`device-footer${isRotate ? ' rotate' : ''}`} />
|
||
</div>
|
||
</div>
|
||
</HideScrollbar>
|
||
|
||
<div className={'switch-device'}>
|
||
<IconOxygenMobile fill={COLOR_FONT_MAIN} />
|
||
<select value={selectedDevice} onChange={handleOnChangeDevice}>
|
||
{devices.map((value) => (
|
||
<option value={value.name}>{value.name}</option>
|
||
))}
|
||
</select>
|
||
<div className={'rotate-device'} title={'旋转屏幕'} onClick={handleOnRotateDevice}>
|
||
{isRotate ? (
|
||
<IconOxygenRotateRight fill={COLOR_FONT_MAIN} />
|
||
) : (
|
||
<IconOxygenRotateLeft fill={COLOR_FONT_MAIN} />
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className={'switch-zoom'}>
|
||
<IconOxygenZoom fill={COLOR_FONT_MAIN} />
|
||
<input
|
||
type={'range'}
|
||
min={0.5}
|
||
max={2}
|
||
step={0.1}
|
||
value={zoom}
|
||
onChange={handleOnChangeZoom}
|
||
/>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<iframe
|
||
data-component={'playground-output-preview-render'}
|
||
key={iframeKey}
|
||
ref={iframeRef}
|
||
src={iframeUrl}
|
||
onLoad={() => setIsLoaded(true)}
|
||
sandbox={'allow-downloads allow-forms allow-modals allow-scripts'}
|
||
allow={'clipboard-read; clipboard-write'}
|
||
/>
|
||
)
|
||
}
|
||
|
||
export default Render
|