Complete main UI #37
@@ -110,6 +110,10 @@ const FileSelector = ({
|
||||
}
|
||||
|
||||
const handleOnValidateTab = (newFileName: string, oldFileName: string) => {
|
||||
if (newFileName.length > 40) {
|
||||
onError?.('File name is too long, maximum 40 characters.')
|
||||
}
|
||||
|
||||
if (!/\.(jsx|tsx|js|ts|css|json)$/.test(newFileName)) {
|
||||
onError?.('Playground only supports *.jsx, *.tsx, *.js, *.ts, *.css, *.json files.')
|
||||
|
||||
@@ -219,4 +223,6 @@ const FileSelector = ({
|
||||
)
|
||||
}
|
||||
|
||||
FileSelector.Item = Item
|
||||
|
||||
export default FileSelector
|
||||
|
||||
@@ -160,4 +160,7 @@ const CodeEditor = ({
|
||||
)
|
||||
}
|
||||
|
||||
CodeEditor.Editor = Editor
|
||||
CodeEditor.FileSelector = FileSelector
|
||||
|
||||
export default CodeEditor
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '@/components/Playground/Output/Preview/render.scss'
|
||||
import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw'
|
||||
|
||||
interface RenderProps {
|
||||
@@ -66,6 +67,7 @@ const Render = ({ iframeKey, compiledCode, onError }: RenderProps) => {
|
||||
|
||||
return (
|
||||
<iframe
|
||||
data-component={'playground-output-preview-render'}
|
||||
key={iframeKey}
|
||||
ref={iframeRef}
|
||||
src={iframeUrl}
|
||||
|
||||
@@ -48,11 +48,6 @@
|
||||
});
|
||||
</script>
|
||||
<script type="module" id="appSrc"></script>
|
||||
<div id="root">
|
||||
<div
|
||||
style="position:absolute;top: 0;left:0;width:100%;height:100%;display: flex;justify-content: center;align-items: center;">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import '@/components/Playground/Output/Preview/preview.scss'
|
||||
import { IFiles, IImportMap } from '@/components/Playground/shared'
|
||||
import Compiler from '@/components/Playground/compiler'
|
||||
import { ENTRY_FILE_NAME } from '@/components/Playground/files'
|
||||
import Render from '@/components/Playground/Output/Preview/Render'
|
||||
|
||||
interface PreviewProps {
|
||||
iframeKey: string
|
||||
files: IFiles
|
||||
importMap: IImportMap
|
||||
entryPoint: string
|
||||
}
|
||||
|
||||
const Preview = ({ iframeKey, files, importMap }: PreviewProps) => {
|
||||
const Preview = ({ iframeKey, files, importMap, entryPoint }: PreviewProps) => {
|
||||
const [errorMsg, setErrorMsg] = useState('')
|
||||
const [compiledCode, setCompiledCode] = useState('')
|
||||
|
||||
@@ -19,7 +19,7 @@ const Preview = ({ iframeKey, files, importMap }: PreviewProps) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
Compiler.compile(files, importMap, [ENTRY_FILE_NAME])
|
||||
Compiler.compile(files, importMap, entryPoint)
|
||||
.then((result) => {
|
||||
setCompiledCode(result.outputFiles[0].text)
|
||||
})
|
||||
@@ -36,4 +36,6 @@ const Preview = ({ iframeKey, files, importMap }: PreviewProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
Preview.Render = Render
|
||||
|
||||
export default Preview
|
||||
|
||||
@@ -3,9 +3,4 @@
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
6
src/components/Playground/Output/Preview/render.scss
Normal file
6
src/components/Playground/Output/Preview/render.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
[data-component=playground-output-preview-render] {
|
||||
border: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import FlexBox from '@/components/common/FlexBox'
|
||||
import { IFiles, IImportMap } from '@/components/Playground/shared'
|
||||
import FileSelector from '@/components/Playground/CodeEditor/FileSelector'
|
||||
import Playground from '@/components/Playground'
|
||||
import Transform from '@/components/Playground/Output/Transform'
|
||||
import Preview from '@/components/Playground/Output/Preview'
|
||||
|
||||
@@ -8,14 +8,15 @@ interface OutputProps {
|
||||
files: IFiles
|
||||
selectedFileName: string
|
||||
importMap: IImportMap
|
||||
entryPoint: string
|
||||
}
|
||||
|
||||
const Output = ({ files, selectedFileName, importMap }: OutputProps) => {
|
||||
const Output = ({ files, selectedFileName, importMap, entryPoint }: OutputProps) => {
|
||||
const [selectedTab, setSelectedTab] = useState('Preview')
|
||||
|
||||
return (
|
||||
<FlexBox data-component={'playground-code-output'}>
|
||||
<FileSelector
|
||||
<Playground.CodeEditor.FileSelector
|
||||
files={{
|
||||
Preview: { name: 'Preview', language: 'json', value: '' },
|
||||
Transform: { name: 'Transform', language: 'json', value: '' }
|
||||
@@ -29,6 +30,7 @@ const Output = ({ files, selectedFileName, importMap }: OutputProps) => {
|
||||
iframeKey={JSON.stringify(importMap)}
|
||||
files={files}
|
||||
importMap={importMap}
|
||||
entryPoint={entryPoint}
|
||||
/>
|
||||
)}
|
||||
{selectedTab === 'Transform' && <Transform file={files[selectedFileName]} />}
|
||||
|
||||
@@ -2,7 +2,7 @@ import esbuild, { Loader, OnLoadArgs, Plugin, PluginBuild } from 'esbuild-wasm'
|
||||
import localforage from 'localforage'
|
||||
import axios from 'axios'
|
||||
import { IFiles, IImportMap } from '@/components/Playground/shared'
|
||||
import { cssToJs, ENTRY_FILE_NAME, jsonToJs, addReactImport } from '@/components/Playground/files'
|
||||
import { cssToJs, jsonToJs, addReactImport } from '@/components/Playground/files'
|
||||
|
||||
class Compiler {
|
||||
private init = false
|
||||
@@ -42,7 +42,7 @@ class Compiler {
|
||||
return esbuild.transform(code, { loader })
|
||||
})
|
||||
|
||||
compile = (files: IFiles, importMap: IImportMap, entryPoints: string[]) =>
|
||||
compile = (files: IFiles, importMap: IImportMap, entryPoint: string) =>
|
||||
new Promise<void>((resolve) => {
|
||||
if (this.init) {
|
||||
resolve()
|
||||
@@ -57,11 +57,11 @@ class Compiler {
|
||||
}).then(() => {
|
||||
return esbuild.build({
|
||||
bundle: true,
|
||||
entryPoints: entryPoints,
|
||||
entryPoints: [entryPoint],
|
||||
format: 'esm',
|
||||
metafile: true,
|
||||
write: false,
|
||||
plugins: [this.fileResolverPlugin(files, importMap, entryPoints)]
|
||||
plugins: [this.fileResolverPlugin(files, importMap, entryPoint)]
|
||||
})
|
||||
})
|
||||
|
||||
@@ -72,13 +72,13 @@ class Compiler {
|
||||
private fileResolverPlugin = (
|
||||
files: IFiles,
|
||||
importMap: IImportMap,
|
||||
entryPoints: string[]
|
||||
entryPoint: string
|
||||
): Plugin => {
|
||||
return {
|
||||
name: 'file-resolver-plugin',
|
||||
setup: (build: PluginBuild) => {
|
||||
build.onResolve({ filter: /.*/ }, (args: esbuild.OnResolveArgs) => {
|
||||
if (entryPoints.includes(args.path)) {
|
||||
if (entryPoint === args.path) {
|
||||
return {
|
||||
namespace: 'OxygenToolbox',
|
||||
path: args.path
|
||||
@@ -165,10 +165,10 @@ class Compiler {
|
||||
})
|
||||
|
||||
build.onLoad({ filter: /.*/ }, async (args: OnLoadArgs) => {
|
||||
if (args.path === ENTRY_FILE_NAME) {
|
||||
if (entryPoint === args.path) {
|
||||
return {
|
||||
loader: 'tsx',
|
||||
contents: addReactImport(files[ENTRY_FILE_NAME].value)
|
||||
contents: addReactImport(files[entryPoint].value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import '@/components/Playground/playground.scss'
|
||||
import { IFiles, IImportMap, ITsconfig } from '@/components/Playground/shared'
|
||||
import {
|
||||
ENTRY_FILE_NAME,
|
||||
IMPORT_MAP_FILE_NAME,
|
||||
MAIN_FILE_NAME,
|
||||
TS_CONFIG_FILE_NAME
|
||||
@@ -13,9 +14,15 @@ interface PlaygroundProps {
|
||||
initFiles: IFiles
|
||||
initImportMapRaw: string
|
||||
initTsconfigRaw: string
|
||||
entryPoint?: string
|
||||
}
|
||||
|
||||
const Playground = ({ initFiles, initImportMapRaw, initTsconfigRaw }: PlaygroundProps) => {
|
||||
const Playground = ({
|
||||
initFiles,
|
||||
initImportMapRaw,
|
||||
initTsconfigRaw,
|
||||
entryPoint = ENTRY_FILE_NAME
|
||||
}: PlaygroundProps) => {
|
||||
const [files, setFiles] = useState(initFiles)
|
||||
const [selectedFileName, setSelectedFileName] = useState(MAIN_FILE_NAME)
|
||||
const [importMapRaw, setImportMapRaw] = useState<string>(initImportMapRaw)
|
||||
@@ -93,7 +100,12 @@ const Playground = ({ initFiles, initImportMapRaw, initTsconfigRaw }: Playground
|
||||
onChangeFileContent={handleOnChangeFileContent}
|
||||
onSelectedFileChange={setSelectedFileName}
|
||||
/>
|
||||
<Output files={files} selectedFileName={selectedFileName} importMap={importMap!} />
|
||||
<Output
|
||||
files={files}
|
||||
selectedFileName={selectedFileName}
|
||||
importMap={importMap!}
|
||||
entryPoint={entryPoint}
|
||||
/>
|
||||
</FlexBox>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ import FitFullscreen from '@/components/common/FitFullscreen'
|
||||
import FlexBox from '@/components/common/FlexBox'
|
||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||
import Card from '@/components/common/Card'
|
||||
import CodeEditor from '@/components/Playground/CodeEditor'
|
||||
import Permission from '@/components/common/Permission'
|
||||
import Playground from '@/components/Playground'
|
||||
|
||||
const Base = () => {
|
||||
const blocker = useBlocker(
|
||||
@@ -283,9 +283,11 @@ const Base = () => {
|
||||
duration: 0
|
||||
})
|
||||
void compiler
|
||||
.compile(files, importMap, [
|
||||
.compile(
|
||||
files,
|
||||
importMap,
|
||||
compileForm.getFieldValue('entryFileName') as string
|
||||
])
|
||||
)
|
||||
.then((result) => {
|
||||
void message.destroy('compiling')
|
||||
void message.loading({
|
||||
@@ -982,7 +984,7 @@ const Base = () => {
|
||||
</Card>
|
||||
{editingFileName && (
|
||||
<Card>
|
||||
<CodeEditor
|
||||
<Playground.CodeEditor
|
||||
files={editingFiles[editingBaseId]}
|
||||
selectedFileName={editingFileName}
|
||||
onSelectedFileChange={() => {}}
|
||||
|
||||
@@ -30,8 +30,8 @@ import FitFullscreen from '@/components/common/FitFullscreen'
|
||||
import FlexBox from '@/components/common/FlexBox'
|
||||
import HideScrollbar from '@/components/common/HideScrollbar'
|
||||
import Card from '@/components/common/Card'
|
||||
import CodeEditor from '@/components/Playground/CodeEditor'
|
||||
import Permission from '@/components/common/Permission'
|
||||
import Playground from '@/components/Playground'
|
||||
|
||||
const Template = () => {
|
||||
const blocker = useBlocker(
|
||||
@@ -866,7 +866,7 @@ const Template = () => {
|
||||
</Card>
|
||||
{editingFileName && (
|
||||
<Card>
|
||||
<CodeEditor
|
||||
<Playground.CodeEditor
|
||||
files={editingFiles[editingTemplateId]}
|
||||
selectedFileName={editingFileName}
|
||||
onSelectedFileChange={() => {}}
|
||||
|
||||
Reference in New Issue
Block a user