Files
oxygen-ui/src/components/Playground/Output/Preview/Render.tsx

157 lines
5.0 KiB
TypeScript

import Icon from '@ant-design/icons'
import { Background, Controls, MiniMap, Node, Panel, ReactFlow } from '@xyflow/react'
import '@xyflow/react/dist/style.css'
import { AppContext } from '@/App'
import useStyles from '@/components/Playground/Output/Preview/render.style'
import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw'
import devices, { DeviceName } from '@/components/Playground/Output/Preview/devices'
import Simulation, { SimulationData } from '@/components/Playground/Output/Preview/Simulation'
interface RenderProps {
iframeKey: string
compiledCode: string
mobileMode?: boolean
globalJsVariables?: Record<string, unknown>
globalCssVariables?: string
}
interface IMessage {
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE' | 'GLOBAL_VARIABLES'
msg: string
data: {
compiledCode?: string
zoom?: number
globalJsVariables?: Record<string, unknown>
globalCssVariables?: string
}
}
const getIframeUrl = (iframeRaw: string) => {
return URL.createObjectURL(new Blob([iframeRaw], { type: 'text/html' }))
}
const iframeUrl = getIframeUrl(iframeRaw)
const Render = ({
iframeKey,
compiledCode,
mobileMode = false,
globalJsVariables,
globalCssVariables
}: RenderProps) => {
const { styles, theme } = useStyles()
const { isDarkMode } = useContext(AppContext)
const iframeRef = useRef<HTMLIFrameElement>(null)
const [isLoaded, setIsLoaded] = useState(false)
const [selectedDevice, setSelectedDevice] = useState<DeviceName>('Pixel 7')
const [isRotate, setIsRotate] = useState(false)
const [nodes, setNodes] = useState<Node<SimulationData>[]>([])
const handleOnRotateDevice = () => {
setIsRotate(!isRotate)
}
const loadGlobalVariables = () => {
iframeRef.current?.contentWindow?.postMessage(
{
type: 'GLOBAL_VARIABLES',
data: { globalJsVariables, globalCssVariables }
} as IMessage,
'*'
)
}
useEffect(() => {
if (!isLoaded) {
return
}
iframeRef.current?.contentWindow?.postMessage(
{
type: 'UPDATE',
data: { compiledCode }
} as IMessage,
'*'
)
loadGlobalVariables()
}, [isLoaded, compiledCode])
useEffect(() => {
if (!isLoaded) {
return
}
loadGlobalVariables()
}, [isLoaded, globalJsVariables, globalCssVariables])
useEffect(() => {
setNodes([
{
id: 'device',
type: 'simulation',
position: { x: 0, y: 0 },
data: {
deviceWidth: devices[selectedDevice].width,
deviceHeight: devices[selectedDevice].height,
isRotate,
iframeKey,
iframeRef,
iframeUrl,
setIsLoaded
}
}
])
}, [selectedDevice, isRotate, iframeKey, iframeRef, iframeUrl, setIsLoaded])
return mobileMode ? (
<>
<ReactFlow
colorMode={isDarkMode ? 'dark' : 'light'}
nodeTypes={{ simulation: Simulation }}
nodes={nodes}
proOptions={{ hideAttribution: true }}
fitView
>
<Background bgColor={theme.colorBgLayout} />
<MiniMap bgColor={theme.colorBgMask} zoomStep={1} pannable zoomable />
<Controls />
<Panel>
<AntdSpace>
<AntdSelect
size={'small'}
options={Object.values(devices).map((item) => ({
label: item.name,
value: item.name
}))}
value={selectedDevice}
onChange={setSelectedDevice}
/>
<AntdButton
size={'small'}
title={'旋转屏幕'}
onClick={handleOnRotateDevice}
icon={
<Icon
component={
isRotate ? IconOxygenRotateRight : IconOxygenRotateLeft
}
/>
}
/>
</AntdSpace>
</Panel>
</ReactFlow>
</>
) : (
<iframe
className={styles.renderRoot}
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