Refactor(Render): Using React Flow to render the mobile simulator
This commit is contained in:
228
package-lock.json
generated
228
package-lock.json
generated
@@ -33,6 +33,7 @@
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@typescript/ata": "^0.9.7",
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"@xyflow/react": "^12.3.2",
|
||||
"antd": "^5.21.6",
|
||||
"antd-style": "^3.7.1",
|
||||
"axios": "^1.7.7",
|
||||
@@ -3288,6 +3289,55 @@
|
||||
"@types/responselike": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-drag": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
|
||||
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-selection": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
|
||||
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-transition": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
|
||||
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-zoom": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
|
||||
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-interpolate": "*",
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
@@ -3736,6 +3786,36 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xyflow/react": {
|
||||
"version": "12.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.3.3.tgz",
|
||||
"integrity": "sha512-hKwnY68fijc393/KCCCyJUi+vESPnY0Vht0Brbny8feMpp/iHtTNGbpwzQtSDmbvvnnIwIAWrxPZPgiUQmK8Kw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@xyflow/system": "0.0.44",
|
||||
"classcat": "^5.0.3",
|
||||
"zustand": "^4.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17",
|
||||
"react-dom": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/@xyflow/system": {
|
||||
"version": "0.0.44",
|
||||
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.44.tgz",
|
||||
"integrity": "sha512-hKHtH8hUVKmCCXbTdEYUWNqRkcSBwYxdzZhIxpJst60AEnlobfphNu8eAOJArEJJl+MrjidvY5K/BOzYUcwCug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-drag": "^3.0.7",
|
||||
"@types/d3-selection": "^3.0.10",
|
||||
"@types/d3-transition": "^3.0.8",
|
||||
"@types/d3-zoom": "^3.0.8",
|
||||
"d3-drag": "^3.0.0",
|
||||
"d3-selection": "^3.0.0",
|
||||
"d3-zoom": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/7zip-bin": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz",
|
||||
@@ -4764,6 +4844,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/classcat": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
|
||||
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
@@ -5242,6 +5328,111 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dispatch": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||
"integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-drag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz",
|
||||
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-selection": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-selection": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-timer": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
|
||||
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-transition": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz",
|
||||
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-color": "1 - 3",
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-ease": "1 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-timer": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"d3-selection": "2 - 3"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-zoom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz",
|
||||
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-drag": "2 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-selection": "2 - 3",
|
||||
"d3-transition": "2 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
|
||||
@@ -11596,6 +11787,15 @@
|
||||
"react": ">= 16.x"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
|
||||
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/utf8-byte-length": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
|
||||
@@ -12055,6 +12255,34 @@
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/zustand": {
|
||||
"version": "4.5.5",
|
||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz",
|
||||
"integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"use-sync-external-store": "1.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.8",
|
||||
"immer": ">=9.0.6",
|
||||
"react": ">=16.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"immer": {
|
||||
"optional": true
|
||||
},
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/zwitch": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@typescript/ata": "^0.9.7",
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"@xyflow/react": "^12.3.2",
|
||||
"antd": "^5.21.6",
|
||||
"antd-style": "^3.7.1",
|
||||
"axios": "^1.7.7",
|
||||
|
||||
@@ -2,9 +2,7 @@ import { createStyles } from 'antd-style'
|
||||
|
||||
export default createStyles(({ token }) => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
position: 'relative'
|
||||
},
|
||||
|
||||
errorMessage: {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { ChangeEvent } from 'react'
|
||||
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 HideScrollbar from '@/components/common/HideScrollbar'
|
||||
import devices, { DeviceName } from '@/components/Playground/Output/Preview/devices'
|
||||
import Simulation, { SimulationData } from '@/components/Playground/Output/Preview/Simulation'
|
||||
|
||||
interface RenderProps {
|
||||
iframeKey: string
|
||||
@@ -10,7 +14,7 @@ interface RenderProps {
|
||||
}
|
||||
|
||||
interface IMessage {
|
||||
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE' | 'SCALE'
|
||||
type: 'LOADED' | 'ERROR' | 'UPDATE' | 'DONE'
|
||||
msg: string
|
||||
data: {
|
||||
compiledCode?: string
|
||||
@@ -18,131 +22,37 @@ interface IMessage {
|
||||
}
|
||||
}
|
||||
|
||||
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' }))
|
||||
return URL.createObjectURL(new Blob([iframeRaw], { type: 'text/html' }))
|
||||
}
|
||||
|
||||
const iframeUrl = getIframeUrl(iframeRaw)
|
||||
|
||||
const Render = ({ iframeKey, compiledCode, mobileMode = false }: RenderProps) => {
|
||||
const { styles, theme, cx } = useStyles()
|
||||
const { styles, theme } = useStyles()
|
||||
const { isDarkMode } = useContext(AppContext)
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null)
|
||||
const [isLoaded, setIsLoaded] = useState(false)
|
||||
const [selectedDevice, setSelectedDevice] = useState('Pixel 7')
|
||||
const [zoom, setZoom] = useState(1)
|
||||
const [selectedDevice, setSelectedDevice] = useState<DeviceName>('Pixel 7')
|
||||
const [isRotate, setIsRotate] = useState(false)
|
||||
|
||||
const devices: IDevice[] = [
|
||||
const nodes: Node<SimulationData>[] = [
|
||||
{
|
||||
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
|
||||
id: 'device',
|
||||
type: 'simulation',
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
deviceWidth: devices[selectedDevice].width,
|
||||
deviceHeight: devices[selectedDevice].height,
|
||||
isRotate,
|
||||
iframeKey,
|
||||
iframeRef,
|
||||
iframeUrl,
|
||||
setIsLoaded
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const handleOnChangeDevice = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
setSelectedDevice(e.target.value)
|
||||
}
|
||||
|
||||
const handleOnChangeZoom = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setZoom(Number(e.target.value))
|
||||
}
|
||||
|
||||
const handleOnRotateDevice = () => {
|
||||
setIsRotate(!isRotate)
|
||||
}
|
||||
@@ -159,98 +69,44 @@ const Render = ({ iframeKey, compiledCode, mobileMode = false }: RenderProps) =>
|
||||
}
|
||||
}, [isLoaded, compiledCode])
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoaded) {
|
||||
iframeRef.current?.contentWindow?.postMessage(
|
||||
{
|
||||
type: 'SCALE',
|
||||
data: { zoom }
|
||||
} as IMessage,
|
||||
'*'
|
||||
)
|
||||
}
|
||||
}, [isLoaded, zoom])
|
||||
|
||||
return mobileMode ? (
|
||||
<>
|
||||
<HideScrollbar
|
||||
className={styles.mobileModeRoot}
|
||||
isShowVerticalScrollbar
|
||||
isShowHorizontalScrollbar
|
||||
autoHideWaitingTime={1000}
|
||||
<ReactFlow
|
||||
colorMode={isDarkMode ? 'dark' : 'light'}
|
||||
nodeTypes={{ simulation: Simulation }}
|
||||
nodes={nodes}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
fitView
|
||||
>
|
||||
<div className={styles.mobileModeContent} style={{ zoom }}>
|
||||
<div className={cx(styles.device, isRotate ? styles.rotate : '')}>
|
||||
<div
|
||||
className={cx(
|
||||
styles.deviceHeader,
|
||||
isRotate ? styles.rotatedDeviceHeader : ''
|
||||
)}
|
||||
<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}
|
||||
/>
|
||||
<div
|
||||
className={cx(
|
||||
styles.deviceContent,
|
||||
isRotate ? styles.rotatedDeviceContent : ''
|
||||
)}
|
||||
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
|
||||
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'}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cx(
|
||||
styles.deviceFooter,
|
||||
isRotate ? styles.rotatedDeviceFooter : ''
|
||||
)}
|
||||
<AntdButton
|
||||
size={'small'}
|
||||
title={'旋转屏幕'}
|
||||
onClick={handleOnRotateDevice}
|
||||
icon={
|
||||
<Icon
|
||||
component={
|
||||
isRotate ? IconOxygenRotateRight : IconOxygenRotateLeft
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</HideScrollbar>
|
||||
|
||||
<div className={styles.switchDevice}>
|
||||
<IconOxygenMobile fill={theme.colorText} />
|
||||
<select value={selectedDevice} onChange={handleOnChangeDevice}>
|
||||
{devices.map((value) => (
|
||||
<option value={value.name}>{value.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<div title={'旋转屏幕'} onClick={handleOnRotateDevice}>
|
||||
{isRotate ? (
|
||||
<IconOxygenRotateRight fill={theme.colorText} />
|
||||
) : (
|
||||
<IconOxygenRotateLeft fill={theme.colorText} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.switchZoom}>
|
||||
<IconOxygenZoom fill={theme.colorText} />
|
||||
<input
|
||||
type={'range'}
|
||||
min={0.5}
|
||||
max={2}
|
||||
step={0.1}
|
||||
value={zoom}
|
||||
onChange={handleOnChangeZoom}
|
||||
/>
|
||||
</div>
|
||||
</AntdSpace>
|
||||
</Panel>
|
||||
</ReactFlow>
|
||||
</>
|
||||
) : (
|
||||
<iframe
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Dispatch, RefObject, SetStateAction } from 'react'
|
||||
import { Node, NodeProps } from '@xyflow/react'
|
||||
import useStyles from '@/components/Playground/Output/Preview/simulation.style'
|
||||
|
||||
export type SimulationData = {
|
||||
deviceWidth: number
|
||||
deviceHeight: number
|
||||
isRotate: boolean
|
||||
iframeKey: string
|
||||
iframeRef: RefObject<HTMLIFrameElement>
|
||||
iframeUrl: string
|
||||
setIsLoaded: Dispatch<SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
export type SimulationNode = Node<SimulationData, 'simulation'>
|
||||
|
||||
const Simulation = ({ data }: NodeProps<SimulationNode>) => {
|
||||
const { styles, cx } = useStyles()
|
||||
|
||||
return (
|
||||
<div className={cx(styles.device, data.isRotate ? styles.rotate : '')}>
|
||||
<div
|
||||
className={cx(styles.deviceHeader, data.isRotate ? styles.rotatedDeviceHeader : '')}
|
||||
/>
|
||||
<div
|
||||
className={cx(
|
||||
styles.deviceContent,
|
||||
data.isRotate ? styles.rotatedDeviceContent : ''
|
||||
)}
|
||||
style={{
|
||||
width: data.isRotate ? data.deviceHeight : data.deviceWidth,
|
||||
height: data.isRotate ? data.deviceWidth : data.deviceHeight
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
className={styles.renderRoot}
|
||||
key={data.iframeKey}
|
||||
ref={data.iframeRef}
|
||||
src={data.iframeUrl}
|
||||
onLoad={() => data.setIsLoaded(true)}
|
||||
sandbox="allow-downloads allow-forms allow-modals allow-scripts"
|
||||
allow={'clipboard-read; clipboard-write'}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cx(styles.deviceFooter, data.isRotate ? styles.rotatedDeviceFooter : '')}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Simulation
|
||||
114
src/renderer/src/components/Playground/Output/Preview/devices.ts
Normal file
114
src/renderer/src/components/Playground/Output/Preview/devices.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
export type DeviceName =
|
||||
| 'iPhone SE'
|
||||
| 'iPhone XR'
|
||||
| 'iPhone 12 Pro'
|
||||
| 'iPhone 14 Pro Max'
|
||||
| 'Pixel 7'
|
||||
| 'Samsung Galaxy S8+'
|
||||
| 'Samsung Galaxy S20 Ultra'
|
||||
| 'iPad Mini'
|
||||
| 'iPad Air'
|
||||
| 'iPad Pro'
|
||||
| 'Surface Pro 7'
|
||||
| 'Surface Duo'
|
||||
| 'Galaxy Fold'
|
||||
| 'Asus Zenbook Fold'
|
||||
| 'Samsung Galaxy A51/71'
|
||||
| 'Nest Hub'
|
||||
| 'Nest Hub Max'
|
||||
|
||||
export interface IDevice {
|
||||
name: DeviceName
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
const devices: Record<DeviceName, IDevice> = {
|
||||
'iPhone SE': {
|
||||
name: 'iPhone SE',
|
||||
width: 375,
|
||||
height: 667
|
||||
},
|
||||
'iPhone XR': {
|
||||
name: 'iPhone XR',
|
||||
width: 414,
|
||||
height: 896
|
||||
},
|
||||
'iPhone 12 Pro': {
|
||||
name: 'iPhone 12 Pro',
|
||||
width: 390,
|
||||
height: 844
|
||||
},
|
||||
'iPhone 14 Pro Max': {
|
||||
name: 'iPhone 14 Pro Max',
|
||||
width: 430,
|
||||
height: 932
|
||||
},
|
||||
'Pixel 7': {
|
||||
name: 'Pixel 7',
|
||||
width: 412,
|
||||
height: 915
|
||||
},
|
||||
'Samsung Galaxy S8+': {
|
||||
name: 'Samsung Galaxy S8+',
|
||||
width: 360,
|
||||
height: 740
|
||||
},
|
||||
'Samsung Galaxy S20 Ultra': {
|
||||
name: 'Samsung Galaxy S20 Ultra',
|
||||
width: 412,
|
||||
height: 915
|
||||
},
|
||||
'iPad Mini': {
|
||||
name: 'iPad Mini',
|
||||
width: 768,
|
||||
height: 1024
|
||||
},
|
||||
'iPad Air': {
|
||||
name: 'iPad Air',
|
||||
width: 820,
|
||||
height: 1180
|
||||
},
|
||||
'iPad Pro': {
|
||||
name: 'iPad Pro',
|
||||
width: 1024,
|
||||
height: 1366
|
||||
},
|
||||
'Surface Pro 7': {
|
||||
name: 'Surface Pro 7',
|
||||
width: 912,
|
||||
height: 1368
|
||||
},
|
||||
'Surface Duo': {
|
||||
name: 'Surface Duo',
|
||||
width: 540,
|
||||
height: 720
|
||||
},
|
||||
'Galaxy Fold': {
|
||||
name: 'Galaxy Fold',
|
||||
width: 280,
|
||||
height: 653
|
||||
},
|
||||
'Asus Zenbook Fold': {
|
||||
name: 'Asus Zenbook Fold',
|
||||
width: 853,
|
||||
height: 1280
|
||||
},
|
||||
'Samsung Galaxy A51/71': {
|
||||
name: 'Samsung Galaxy A51/71',
|
||||
width: 412,
|
||||
height: 914
|
||||
},
|
||||
'Nest Hub': {
|
||||
name: 'Nest Hub',
|
||||
width: 1024,
|
||||
height: 600
|
||||
},
|
||||
'Nest Hub Max': {
|
||||
name: 'Nest Hub Max',
|
||||
width: 1280,
|
||||
height: 800
|
||||
}
|
||||
}
|
||||
|
||||
export default devices
|
||||
@@ -4,7 +4,6 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Preview</title>
|
||||
<!-- es-module-shims -->
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
@@ -36,10 +35,6 @@
|
||||
document.body.appendChild(script);
|
||||
URL.revokeObjectURL(oldSrc);
|
||||
}
|
||||
|
||||
if (data?.type === "SCALE") {
|
||||
document.getElementById("root").style.zoom = data.data.zoom;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script type="module" id="appSrc"></script>
|
||||
|
||||
@@ -1,84 +1,10 @@
|
||||
import { createStyles } from 'antd-style'
|
||||
|
||||
export default createStyles(({ token }) => ({
|
||||
export default createStyles(() => ({
|
||||
renderRoot: {
|
||||
border: 'none',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
flex: 1
|
||||
},
|
||||
|
||||
mobileModeRoot: {
|
||||
backgroundColor: token.colorBgLayout
|
||||
},
|
||||
|
||||
mobileModeContent: {
|
||||
padding: 80
|
||||
},
|
||||
|
||||
device: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: token.colorBgElevated,
|
||||
width: 'fit-content',
|
||||
margin: '0 auto',
|
||||
borderRadius: 40
|
||||
},
|
||||
|
||||
rotate: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
deviceHeader: {
|
||||
margin: '20px auto',
|
||||
width: 60,
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
backgroundColor: token.colorBgMask
|
||||
},
|
||||
|
||||
rotatedDeviceHeader: {
|
||||
margin: 'auto 20px',
|
||||
width: 10,
|
||||
height: 60
|
||||
},
|
||||
|
||||
deviceContent: {
|
||||
margin: '0 10px',
|
||||
backgroundColor: token.colorBgLayout
|
||||
},
|
||||
|
||||
rotatedDeviceContent: {
|
||||
margin: '10px 0'
|
||||
},
|
||||
|
||||
deviceFooter: {
|
||||
margin: '20px auto',
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: token.colorBgMask
|
||||
},
|
||||
|
||||
rotatedDeviceFooter: {
|
||||
margin: 'auto 20px'
|
||||
},
|
||||
|
||||
switchDevice: {
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
left: 10,
|
||||
alignItems: 'center',
|
||||
gap: 4
|
||||
},
|
||||
|
||||
switchZoom: {
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
top: 10,
|
||||
right: 10,
|
||||
alignItems: 'center',
|
||||
gap: 4
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { createStyles } from 'antd-style'
|
||||
|
||||
export default createStyles(({ token }) => ({
|
||||
renderRoot: {
|
||||
border: 'none',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
flex: 1
|
||||
},
|
||||
|
||||
device: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: token.colorBgElevated,
|
||||
width: 'fit-content',
|
||||
margin: '0 auto',
|
||||
borderRadius: 40
|
||||
},
|
||||
|
||||
rotate: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
deviceHeader: {
|
||||
margin: '20px auto',
|
||||
width: 60,
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
backgroundColor: token.colorBgMask
|
||||
},
|
||||
|
||||
rotatedDeviceHeader: {
|
||||
margin: 'auto 20px',
|
||||
width: 10,
|
||||
height: 60
|
||||
},
|
||||
|
||||
deviceContent: {
|
||||
margin: '0 10px',
|
||||
backgroundColor: token.colorBgLayout
|
||||
},
|
||||
|
||||
rotatedDeviceContent: {
|
||||
margin: '10px 0'
|
||||
},
|
||||
|
||||
deviceFooter: {
|
||||
margin: '20px auto',
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: token.colorBgMask
|
||||
},
|
||||
|
||||
rotatedDeviceFooter: {
|
||||
margin: 'auto 20px'
|
||||
}
|
||||
}))
|
||||
Reference in New Issue
Block a user