Complete main UI #37
14
package-lock.json
generated
14
package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
|
"esbuild-wasm": "^0.19.11",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fflate": "^0.8.1",
|
"fflate": "^0.8.1",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
@@ -2228,7 +2229,7 @@
|
|||||||
"resolved": "https://registry.npmmirror.com/@typescript/ata/-/ata-0.9.4.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript/ata/-/ata-0.9.4.tgz",
|
||||||
"integrity": "sha512-PaJ16WouPV/SaA+c0tnOKIqYq24+m93ipl/e0Dkxuianer+ibc5b0/6ZgfCFF8J7QEp57dySMSP9nWOFaCfJnw==",
|
"integrity": "sha512-PaJ16WouPV/SaA+c0tnOKIqYq24+m93ipl/e0Dkxuianer+ibc5b0/6ZgfCFF8J7QEp57dySMSP9nWOFaCfJnw==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^4.4.4"
|
"typescript": ">=4.4.4 <6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ungap/structured-clone": {
|
"node_modules/@ungap/structured-clone": {
|
||||||
@@ -3302,6 +3303,17 @@
|
|||||||
"@esbuild/win32-x64": "0.19.8"
|
"@esbuild/win32-x64": "0.19.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/esbuild-wasm": {
|
||||||
|
"version": "0.19.11",
|
||||||
|
"resolved": "https://registry.npmmirror.com/esbuild-wasm/-/esbuild-wasm-0.19.11.tgz",
|
||||||
|
"integrity": "sha512-MIhnpc1TxERUHomteO/ZZHp+kUawGEc03D/8vMHGzffLvbFLeDe6mwxqEZwlqBNY7SLWbyp6bBQAcCen8+wpjQ==",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
|
"esbuild-wasm": "^0.19.11",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fflate": "^0.8.1",
|
"fflate": "^0.8.1",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
|
|||||||
78
src/components/Playground/Preview/index.tsx
Normal file
78
src/components/Playground/Preview/index.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import React, { useRef, useState } from 'react'
|
||||||
|
import { IFiles } from '@/components/Playground/shared.ts'
|
||||||
|
import { useUpdatedEffect } from '@/util/hooks.tsx'
|
||||||
|
import Compiler from '@/components/Playground/compiler.ts'
|
||||||
|
import { Loader } from 'esbuild-wasm'
|
||||||
|
import { cssToJs, jsonToJs } from '@/components/Playground/files.ts'
|
||||||
|
|
||||||
|
interface OutputProps {
|
||||||
|
files: IFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
const Preview: React.FC<OutputProps> = ({ files }) => {
|
||||||
|
const compiler = useRef<Compiler>()
|
||||||
|
const [compileCode, setCompileCode] = useState('')
|
||||||
|
const [fileName, setFileName] = useState('main.tsx')
|
||||||
|
|
||||||
|
const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFileName(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
useUpdatedEffect(() => {
|
||||||
|
if (!compiler.current) {
|
||||||
|
compiler.current = new Compiler()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useUpdatedEffect(() => {
|
||||||
|
if (files[fileName]) {
|
||||||
|
try {
|
||||||
|
const file = files[fileName]
|
||||||
|
|
||||||
|
let loader: Loader
|
||||||
|
let code = file.value
|
||||||
|
|
||||||
|
switch (file.language) {
|
||||||
|
case 'typescript':
|
||||||
|
loader = 'tsx'
|
||||||
|
break
|
||||||
|
case 'javascript':
|
||||||
|
loader = 'jsx'
|
||||||
|
break
|
||||||
|
case 'css':
|
||||||
|
code = cssToJs(file)
|
||||||
|
loader = 'js'
|
||||||
|
break
|
||||||
|
case 'json':
|
||||||
|
code = jsonToJs(file)
|
||||||
|
loader = 'js'
|
||||||
|
break
|
||||||
|
case 'xml':
|
||||||
|
loader = 'default'
|
||||||
|
}
|
||||||
|
|
||||||
|
compiler.current
|
||||||
|
?.transform(code, loader)
|
||||||
|
.then((value) => {
|
||||||
|
setCompileCode(value.code)
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('编译失败', e)
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
setCompileCode('')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setCompileCode('')
|
||||||
|
}
|
||||||
|
}, [fileName, files])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AntdInput.TextArea value={compileCode} />
|
||||||
|
<AntdInput value={fileName} onChange={handleOnChange} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Preview
|
||||||
30
src/components/Playground/compiler.ts
Normal file
30
src/components/Playground/compiler.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import esbuild, { Loader } from 'esbuild-wasm'
|
||||||
|
import wasm from 'esbuild-wasm/esbuild.wasm?url'
|
||||||
|
|
||||||
|
class Compiler {
|
||||||
|
private init = false
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
try {
|
||||||
|
void esbuild.initialize({ worker: true, wasmURL: wasm }).then(() => {
|
||||||
|
this.init = true
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transform = (code: string, loader: Loader) =>
|
||||||
|
new Promise<boolean>((resolve) => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (this.init) {
|
||||||
|
clearInterval(timer)
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
}).then(() => {
|
||||||
|
return esbuild.transform(code, { loader })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Compiler
|
||||||
@@ -3,7 +3,7 @@ import importMap from '@/components/Playground/template/import-map.json?raw'
|
|||||||
import AppCss from '@/components/Playground/template/src/App.css?raw'
|
import AppCss from '@/components/Playground/template/src/App.css?raw'
|
||||||
import App from '@/components/Playground/template/src/App.tsx?raw'
|
import App from '@/components/Playground/template/src/App.tsx?raw'
|
||||||
import main from '@/components/Playground/template/src/main.tsx?raw'
|
import main from '@/components/Playground/template/src/main.tsx?raw'
|
||||||
import { IFiles } from '@/components/Playground/shared'
|
import { IFile, IFiles } from '@/components/Playground/shared'
|
||||||
|
|
||||||
export const MAIN_FILE_NAME = 'App.tsx'
|
export const MAIN_FILE_NAME = 'App.tsx'
|
||||||
export const IMPORT_MAP_FILE_NAME = 'import-map.json'
|
export const IMPORT_MAP_FILE_NAME = 'import-map.json'
|
||||||
@@ -51,6 +51,40 @@ export const getFilesFromUrl = () => {
|
|||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getModuleFile = (files: IFiles, moduleName: string) => {
|
||||||
|
let _moduleName = moduleName.split('./').pop() || ''
|
||||||
|
if (!_moduleName.includes('.')) {
|
||||||
|
const realModuleName = Object.keys(files).find((key) =>
|
||||||
|
key.split('.').includes(_moduleName)
|
||||||
|
)
|
||||||
|
if (realModuleName) _moduleName = realModuleName
|
||||||
|
}
|
||||||
|
return files[_moduleName]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const jsonToJs = (file: IFile) => {
|
||||||
|
const js = `export default ${file.value}`
|
||||||
|
return URL.createObjectURL(new Blob([js], { type: 'application/javascript' }))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cssToJs = (file: IFile) => {
|
||||||
|
const randomId = new Date().getTime()
|
||||||
|
const js = `
|
||||||
|
(() => {
|
||||||
|
let stylesheet = document.getElementById('style_${randomId}_${file.name}');
|
||||||
|
if (!stylesheet) {
|
||||||
|
stylesheet = document.createElement('style')
|
||||||
|
stylesheet.setAttribute('id', 'style_${randomId}_${file.name}')
|
||||||
|
document.head.appendChild(stylesheet)
|
||||||
|
}
|
||||||
|
const styles = document.createTextNode(\`${file.value}\`)
|
||||||
|
stylesheet.innerHTML = ''
|
||||||
|
stylesheet.appendChild(styles)
|
||||||
|
})()
|
||||||
|
`
|
||||||
|
return URL.createObjectURL(new Blob([js], { type: 'application/javascript' }))
|
||||||
|
}
|
||||||
|
|
||||||
export const initFiles: IFiles = getFilesFromUrl() || {
|
export const initFiles: IFiles = getFilesFromUrl() || {
|
||||||
[ENTRY_FILE_NAME]: {
|
[ENTRY_FILE_NAME]: {
|
||||||
name: ENTRY_FILE_NAME,
|
name: ENTRY_FILE_NAME,
|
||||||
|
|||||||
@@ -2,12 +2,17 @@ import React from 'react'
|
|||||||
import CodeEditor from '@/components/Playground/CodeEditor'
|
import CodeEditor from '@/components/Playground/CodeEditor'
|
||||||
import { initFiles } from '@/components/Playground/files'
|
import { initFiles } from '@/components/Playground/files'
|
||||||
import { IFiles } from '@/components/Playground/shared'
|
import { IFiles } from '@/components/Playground/shared'
|
||||||
|
import FitFullscreen from '@/components/common/FitFullscreen.tsx'
|
||||||
|
import FlexBox from '@/components/common/FlexBox.tsx'
|
||||||
|
import Preview from '@/components/Playground/Preview'
|
||||||
|
|
||||||
const OnlineEditor: React.FC = () => {
|
const OnlineEditor: React.FC = () => {
|
||||||
const [files, setFiles] = useState<IFiles>(initFiles)
|
const [files, setFiles] = useState<IFiles>(initFiles)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<FitFullscreen>
|
||||||
|
<FlexBox style={{ width: '100%', height: '100%' }} direction={'horizontal'}>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
files={files}
|
files={files}
|
||||||
onAddFile={(_, files) => setFiles(files)}
|
onAddFile={(_, files) => setFiles(files)}
|
||||||
@@ -15,6 +20,9 @@ const OnlineEditor: React.FC = () => {
|
|||||||
onRenameFile={(_, __, files) => setFiles(files)}
|
onRenameFile={(_, __, files) => setFiles(files)}
|
||||||
onChangeFileContent={(_, __, files) => setFiles(files)}
|
onChangeFileContent={(_, __, files) => setFiles(files)}
|
||||||
/>
|
/>
|
||||||
|
<Preview files={files} />
|
||||||
|
</FlexBox>
|
||||||
|
</FitFullscreen>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user