Complete main UI #37
@@ -37,7 +37,7 @@ const Editor: React.FC<EditorProps> = ({
|
||||
dispose: () => undefined
|
||||
})
|
||||
const { total, finished, onWatch } = useTypesProgress()
|
||||
const file = files[selectedFileName] ?? { name: 'Untitled' }
|
||||
const file = files[selectedFileName] || { name: 'Untitled' }
|
||||
|
||||
const handleOnEditorDidMount = (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => {
|
||||
editorRef.current = editor
|
||||
|
||||
@@ -46,7 +46,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
|
||||
(item) => ![IMPORT_MAP_FILE_NAME, ENTRY_FILE_NAME].includes(item) && !files[item].hidden
|
||||
)
|
||||
const propsSelectedFileName =
|
||||
props.selectedFileName ?? (filteredFilesName.length ? filteredFilesName[0] : '')
|
||||
props.selectedFileName || (filteredFilesName.length ? filteredFilesName[0] : '')
|
||||
const [selectedFileName, setSelectedFileName] = useState(propsSelectedFileName)
|
||||
const [errorMsg, setErrorMsg] = useState('')
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
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
|
||||
77
src/components/Playground/Transform/index.tsx
Normal file
77
src/components/Playground/Transform/index.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react'
|
||||
import MonacoEditor from '@monaco-editor/react'
|
||||
import { Loader } from 'esbuild-wasm'
|
||||
import { useUpdatedEffect } from '@/util/hooks'
|
||||
import { IFile, ITheme } from '@/components/Playground/shared'
|
||||
import Compiler from '@/components/Playground/compiler'
|
||||
import { cssToJs, jsonToJs } from '@/components/Playground/files'
|
||||
import { MonacoEditorConfig } from '@/components/Playground/CodeEditor/Editor/monacoConfig'
|
||||
|
||||
interface OutputProps {
|
||||
file: IFile
|
||||
theme?: ITheme
|
||||
}
|
||||
|
||||
const Preview: React.FC<OutputProps> = ({ file, theme }) => {
|
||||
const compiler = useRef<Compiler>()
|
||||
const [compileCode, setCompileCode] = useState('')
|
||||
|
||||
useUpdatedEffect(() => {
|
||||
if (!compiler.current) {
|
||||
compiler.current = new Compiler()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const compile = (code: string, loader: Loader) => {
|
||||
compiler.current
|
||||
?.transform(code, loader)
|
||||
.then((value) => {
|
||||
setCompileCode(value.code)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('编译失败', e)
|
||||
})
|
||||
}
|
||||
|
||||
useUpdatedEffect(() => {
|
||||
if (file) {
|
||||
try {
|
||||
const code = file.value
|
||||
|
||||
switch (file.language) {
|
||||
case 'typescript':
|
||||
compile(code, 'tsx')
|
||||
break
|
||||
case 'javascript':
|
||||
compile(code, 'jsx')
|
||||
break
|
||||
case 'css':
|
||||
setCompileCode(cssToJs(file))
|
||||
break
|
||||
case 'json':
|
||||
setCompileCode(jsonToJs(file))
|
||||
break
|
||||
case 'xml':
|
||||
setCompileCode(code)
|
||||
}
|
||||
} catch (e) {
|
||||
setCompileCode('')
|
||||
}
|
||||
} else {
|
||||
setCompileCode('')
|
||||
}
|
||||
}, [file])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MonacoEditor
|
||||
theme={theme}
|
||||
language={'javascript'}
|
||||
value={compileCode}
|
||||
options={{ ...MonacoEditorConfig, readOnly: false }}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Preview
|
||||
@@ -9,9 +9,7 @@ class Compiler {
|
||||
void esbuild.initialize({ worker: true, wasmURL: wasm }).then(() => {
|
||||
this.init = true
|
||||
})
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
transform = (code: string, loader: Loader) =>
|
||||
|
||||
@@ -3,18 +3,19 @@ import importMap from '@/components/Playground/template/import-map.json?raw'
|
||||
import AppCss from '@/components/Playground/template/src/App.css?raw'
|
||||
import App from '@/components/Playground/template/src/App.tsx?raw'
|
||||
import main from '@/components/Playground/template/src/main.tsx?raw'
|
||||
import { IFile, IFiles } from '@/components/Playground/shared'
|
||||
import { IFile, IFiles, ILanguage } from '@/components/Playground/shared'
|
||||
|
||||
export const MAIN_FILE_NAME = 'App.tsx'
|
||||
export const IMPORT_MAP_FILE_NAME = 'import-map.json'
|
||||
export const ENTRY_FILE_NAME = 'main.tsx'
|
||||
|
||||
export const fileNameToLanguage = (name: string) => {
|
||||
export const fileNameToLanguage = (name: string): ILanguage => {
|
||||
const suffix = name.split('.').pop() || ''
|
||||
if (['js', 'jsx'].includes(suffix)) return 'javascript'
|
||||
if (['ts', 'tsx'].includes(suffix)) return 'typescript'
|
||||
if (['json'].includes(suffix)) return 'json'
|
||||
if (['css'].includes(suffix)) return 'css'
|
||||
if (['svg'].includes(suffix)) return 'xml'
|
||||
return 'javascript'
|
||||
}
|
||||
|
||||
@@ -62,27 +63,30 @@ export const getModuleFile = (files: IFiles, moduleName: string) => {
|
||||
return files[_moduleName]
|
||||
}
|
||||
|
||||
export const jsToBlob = (code: string) => {
|
||||
return URL.createObjectURL(new Blob([code], { type: 'application/javascript' }))
|
||||
}
|
||||
|
||||
export const jsonToJs = (file: IFile) => {
|
||||
const js = `export default ${file.value}`
|
||||
return URL.createObjectURL(new Blob([js], { type: 'application/javascript' }))
|
||||
return `export default ${file.value}`
|
||||
}
|
||||
|
||||
export const cssToJs = (file: IFile) => {
|
||||
const randomId = new Date().getTime()
|
||||
const js = `
|
||||
(() => {
|
||||
return `(() => {
|
||||
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}\`)
|
||||
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() || {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import CodeEditor from '@/components/Playground/CodeEditor'
|
||||
import { initFiles } from '@/components/Playground/files'
|
||||
import { initFiles, MAIN_FILE_NAME } from '@/components/Playground/files'
|
||||
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'
|
||||
import FitFullscreen from '@/components/common/FitFullscreen'
|
||||
import FlexBox from '@/components/common/FlexBox'
|
||||
import Transform from '@/components/Playground/Transform'
|
||||
|
||||
const OnlineEditor: React.FC = () => {
|
||||
const [files, setFiles] = useState<IFiles>(initFiles)
|
||||
const [selectedFileName, setSelectedFileName] = useState(MAIN_FILE_NAME)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -15,12 +16,14 @@ const OnlineEditor: React.FC = () => {
|
||||
<FlexBox style={{ width: '100%', height: '100%' }} direction={'horizontal'}>
|
||||
<CodeEditor
|
||||
files={files}
|
||||
onSelectedFileChange={setSelectedFileName}
|
||||
selectedFileName={selectedFileName}
|
||||
onAddFile={(_, files) => setFiles(files)}
|
||||
onRemoveFile={(_, files) => setFiles(files)}
|
||||
onRenameFile={(_, __, files) => setFiles(files)}
|
||||
onChangeFileContent={(_, __, files) => setFiles(files)}
|
||||
/>
|
||||
<Preview files={files} />
|
||||
<Transform file={files[selectedFileName]} />
|
||||
</FlexBox>
|
||||
</FitFullscreen>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user