From ed4cef1c78f8fc0c0e825c4b7ffff18d15a9500c Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Fri, 12 Jan 2024 16:57:25 +0800 Subject: [PATCH] Finish Playground --- .../CodeEditor/FileSelector/index.tsx | 7 ++- .../Playground/CodeEditor/code-editor.scss | 11 +--- .../Playground/CodeEditor/index.tsx | 5 +- .../{ => Output}/Preview/iframe.html | 6 +-- .../Playground/{ => Output}/Preview/index.tsx | 21 ++++---- .../{ => Output}/Preview/preview.scss | 1 + .../{ => Output}/Transform/index.tsx | 24 +++++---- .../Output/Transform/transform.scss | 3 ++ src/components/Playground/Output/index.tsx | 40 ++++++++++++++ src/components/Playground/files.ts | 14 ++--- src/components/Playground/index.tsx | 52 +++++++++++++++++-- src/components/Playground/playground.scss | 18 +++++++ src/pages/OnlineEditor.tsx | 25 ++------- 13 files changed, 156 insertions(+), 71 deletions(-) rename src/components/Playground/{ => Output}/Preview/iframe.html (88%) rename src/components/Playground/{ => Output}/Preview/index.tsx (82%) rename src/components/Playground/{ => Output}/Preview/preview.scss (82%) rename src/components/Playground/{ => Output}/Transform/index.tsx (73%) create mode 100644 src/components/Playground/Output/Transform/transform.scss create mode 100644 src/components/Playground/Output/index.tsx create mode 100644 src/components/Playground/playground.scss diff --git a/src/components/Playground/CodeEditor/FileSelector/index.tsx b/src/components/Playground/CodeEditor/FileSelector/index.tsx index bdcb004..ac8e15d 100644 --- a/src/components/Playground/CodeEditor/FileSelector/index.tsx +++ b/src/components/Playground/CodeEditor/FileSelector/index.tsx @@ -64,10 +64,10 @@ const FileSelector: React.FC = ({ } const handleOnCancel = () => { + onError?.('') if (!creating) { return } - tabs.pop() setTabs([...tabs]) setCreating(false) @@ -111,7 +111,10 @@ const FileSelector: React.FC = ({ return false } - if (tabs.includes(newFileName) && newFileName !== oldFileName) { + if ( + tabs.map((item) => item.toLowerCase()).includes(newFileName.toLowerCase()) && + newFileName.toLowerCase() !== oldFileName.toLowerCase() + ) { onError?.(`File "${newFileName}" already exists.`) return false } diff --git a/src/components/Playground/CodeEditor/code-editor.scss b/src/components/Playground/CodeEditor/code-editor.scss index d8da64c..3887677 100644 --- a/src/components/Playground/CodeEditor/code-editor.scss +++ b/src/components/Playground/CodeEditor/code-editor.scss @@ -1,14 +1,5 @@ [data-component=playground-code-editor] { + position: relative; width: 100%; height: 100%; - - .playground-code-editor-message { - position: absolute; - bottom: 0; - width: 100%; - color: white; - background-color: #FF4D4FAA; - padding: 5px 10px; - font-size: 1.6em; - } } \ No newline at end of file diff --git a/src/components/Playground/CodeEditor/index.tsx b/src/components/Playground/CodeEditor/index.tsx index 05f8b9f..35fad62 100644 --- a/src/components/Playground/CodeEditor/index.tsx +++ b/src/components/Playground/CodeEditor/index.tsx @@ -3,7 +3,6 @@ import _ from 'lodash' import '@/components/Playground/CodeEditor/code-editor.scss' import { IEditorOptions, IFiles, ITheme } from '@/components/Playground/shared' import { - ENTRY_FILE_NAME, fileNameToLanguage, getFileNameList, IMPORT_MAP_FILE_NAME @@ -44,7 +43,7 @@ const CodeEditor: React.FC = ({ ...props }) => { const filteredFilesName = getFileNameList(files).filter( - (item) => ![IMPORT_MAP_FILE_NAME, ENTRY_FILE_NAME].includes(item) && !files[item].hidden + (item) => ![IMPORT_MAP_FILE_NAME].includes(item) && !files[item].hidden ) const propsSelectedFileName = props.selectedFileName || (filteredFilesName.length ? filteredFilesName[0] : '') @@ -148,7 +147,7 @@ const CodeEditor: React.FC = ({ onChange={handleOnChangeFileContent} onJumpFile={handleOnChangeSelectedFile} /> - {errorMsg &&
{errorMsg}
} + {errorMsg &&
{errorMsg}
} ) diff --git a/src/components/Playground/Preview/iframe.html b/src/components/Playground/Output/Preview/iframe.html similarity index 88% rename from src/components/Playground/Preview/iframe.html rename to src/components/Playground/Output/Preview/iframe.html index 9257ece..b485bf9 100644 --- a/src/components/Playground/Preview/iframe.html +++ b/src/components/Playground/Output/Preview/iframe.html @@ -9,11 +9,11 @@ diff --git a/src/components/Playground/Preview/index.tsx b/src/components/Playground/Output/Preview/index.tsx similarity index 82% rename from src/components/Playground/Preview/index.tsx rename to src/components/Playground/Output/Preview/index.tsx index f3b1325..17e310f 100644 --- a/src/components/Playground/Preview/index.tsx +++ b/src/components/Playground/Output/Preview/index.tsx @@ -1,14 +1,14 @@ import React, { useRef, useState } from 'react' -import { IFiles } from '@/components/Playground/shared' -import iframeRaw from '@/components/Playground/Preview/iframe.html?raw' +import { IFiles, IImportMap } from '@/components/Playground/shared' +import iframeRaw from '@/components/Playground/Output/Preview/iframe.html?raw' import { useUpdatedEffect } from '@/util/hooks' -import { IMPORT_MAP_FILE_NAME } from '@/components/Playground/files' import Compiler from '@/components/Playground/compiler' -import '@/components/Playground/Preview/preview.scss' +import '@/components/Playground/Output/Preview/preview.scss' interface PreviewProps { iframeKey: string files: IFiles + importMap: IImportMap } interface IMessage { @@ -34,7 +34,7 @@ const getIframeUrl = (iframeRaw: string) => { const iframeUrl = getIframeUrl(iframeRaw) -const Preview: React.FC = ({ iframeKey, files }) => { +const Preview: React.FC = ({ iframeKey, files, importMap }) => { const iframeRef = useRef(null) const [errorMsg, setErrorMsg] = useState('') const [loaded, setLoaded] = useState(false) @@ -48,15 +48,11 @@ const Preview: React.FC = ({ iframeKey, files }) => { case 'ERROR': setErrorMsg(msg) break - default: + case 'DONE': setErrorMsg('') } } - useEffect(() => { - console.error(errorMsg) - }, [errorMsg]) - useUpdatedEffect(() => { window.addEventListener('message', handleMessage) @@ -66,7 +62,7 @@ const Preview: React.FC = ({ iframeKey, files }) => { }, []) useUpdatedEffect(() => { - Compiler.compile(files, JSON.parse(files[IMPORT_MAP_FILE_NAME].value)) + Compiler.compile(files, importMap) .then((result) => { if (loaded) { iframeRef.current?.contentWindow?.postMessage({ @@ -76,7 +72,7 @@ const Preview: React.FC = ({ iframeKey, files }) => { } }) .catch((e) => { - setErrorMsg(e) + setErrorMsg(`编译失败:${e.message}`) }) }, [files, Compiler, loaded]) @@ -88,6 +84,7 @@ const Preview: React.FC = ({ iframeKey, files }) => { src={iframeUrl} sandbox="allow-popups-to-escape-sandbox allow-scripts allow-popups allow-forms allow-pointer-lock allow-top-navigation allow-modals allow-same-origin" /> + {errorMsg &&
{errorMsg}
} ) } diff --git a/src/components/Playground/Preview/preview.scss b/src/components/Playground/Output/Preview/preview.scss similarity index 82% rename from src/components/Playground/Preview/preview.scss rename to src/components/Playground/Output/Preview/preview.scss index 5836dce..b7da9e6 100644 --- a/src/components/Playground/Preview/preview.scss +++ b/src/components/Playground/Output/Preview/preview.scss @@ -1,5 +1,6 @@ [data-component=playground-preview] { display: flex; + position: relative; iframe { border: none; flex: 1; diff --git a/src/components/Playground/Transform/index.tsx b/src/components/Playground/Output/Transform/index.tsx similarity index 73% rename from src/components/Playground/Transform/index.tsx rename to src/components/Playground/Output/Transform/index.tsx index 23dd8be..1b01cf8 100644 --- a/src/components/Playground/Transform/index.tsx +++ b/src/components/Playground/Output/Transform/index.tsx @@ -1,12 +1,13 @@ -import React from 'react' +import React, { useState } 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' -import { addReactImport } from '@/components/Playground/utils' +import '@/components/Playground/Output/Transform/transform.scss' +import { useUpdatedEffect } from '@/util/hooks.tsx' +import { IFile, ITheme } from '@/components/Playground/shared.ts' +import Compiler from '@/components/Playground/compiler.ts' +import { cssToJs, jsonToJs } from '@/components/Playground/files.ts' +import { MonacoEditorConfig } from '@/components/Playground/CodeEditor/Editor/monacoConfig.ts' +import { addReactImport } from '@/components/Playground/utils.ts' interface OutputProps { file: IFile @@ -15,6 +16,7 @@ interface OutputProps { const Transform: React.FC = ({ file, theme }) => { const [compiledCode, setCompiledCode] = useState('') + const [errorMsg, setErrorMsg] = useState('') const compile = (code: string, loader: Loader) => { let _code = code @@ -25,9 +27,10 @@ const Transform: React.FC = ({ file, theme }) => { Compiler?.transform(_code, loader) .then((value) => { setCompiledCode(value.code) + setErrorMsg('') }) .catch((e) => { - console.error('编译失败', e) + setErrorMsg(`编译失败:${e.message}`) }) } @@ -62,14 +65,15 @@ const Transform: React.FC = ({ file, theme }) => { }, [file, Compiler]) return ( - <> +
- + {errorMsg &&
{errorMsg}
} +
) } diff --git a/src/components/Playground/Output/Transform/transform.scss b/src/components/Playground/Output/Transform/transform.scss new file mode 100644 index 0000000..f54a125 --- /dev/null +++ b/src/components/Playground/Output/Transform/transform.scss @@ -0,0 +1,3 @@ +[data-component=playground-transform] { + position: relative; +} \ No newline at end of file diff --git a/src/components/Playground/Output/index.tsx b/src/components/Playground/Output/index.tsx new file mode 100644 index 0000000..680efb1 --- /dev/null +++ b/src/components/Playground/Output/index.tsx @@ -0,0 +1,40 @@ +import React, { useState } from 'react' +import FlexBox from '@/components/common/FlexBox.tsx' +import FileSelector from '@/components/Playground/CodeEditor/FileSelector' +import Transform from '@/components/Playground/Output/Transform' +import { IFiles, IImportMap } from '@/components/Playground/shared.ts' +import Preview from '@/components/Playground/Output/Preview' + +interface OutputProps { + files: IFiles + selectedFileName: string + importMap: IImportMap +} + +const Output: React.FC = ({ files, selectedFileName, importMap }) => { + const [selectedTab, setSelectedTab] = useState('Preview') + + return ( + + setSelectedTab(tabName)} + readonly + /> + {selectedTab === 'Preview' && ( + + )} + {selectedTab === 'Transform' && } + + ) +} + +export default Output diff --git a/src/components/Playground/files.ts b/src/components/Playground/files.ts index 69a1396..b1138e0 100644 --- a/src/components/Playground/files.ts +++ b/src/components/Playground/files.ts @@ -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 App from '@/components/Playground/template/src/App.tsx?raw' import main from '@/components/Playground/template/src/main.tsx?raw' -import { IFile, IFiles, ILanguage } from '@/components/Playground/shared' +import { IFile, IFiles, IImportMap, ILanguage } from '@/components/Playground/shared' export const MAIN_FILE_NAME = 'App.tsx' export const IMPORT_MAP_FILE_NAME = 'import-map.json' @@ -106,11 +106,13 @@ export const initFiles: IFiles = getFilesFromUrl() || { name: 'App.css', language: 'css', value: AppCss - }, - [IMPORT_MAP_FILE_NAME]: { - name: IMPORT_MAP_FILE_NAME, - language: fileNameToLanguage(IMPORT_MAP_FILE_NAME), - value: importMap + } +} + +export const initImportMap: IImportMap = { + imports: { + react: 'https://esm.sh/react@18.2.0', + 'react-dom/client': 'https://esm.sh/react-dom@18.2.0' } } diff --git a/src/components/Playground/index.tsx b/src/components/Playground/index.tsx index c041da6..1aa0bc2 100644 --- a/src/components/Playground/index.tsx +++ b/src/components/Playground/index.tsx @@ -1,7 +1,51 @@ -import React from 'react' +import React, { useState } from 'react' +import '@/components/Playground/playground.scss' +import FlexBox from '@/components/common/FlexBox' +import CodeEditor from '@/components/Playground/CodeEditor' +import Output from '@/components/Playground/Output' +import { IFiles, IImportMap } from '@/components/Playground/shared.ts' +import { IMPORT_MAP_FILE_NAME, MAIN_FILE_NAME } from '@/components/Playground/files.ts' -const ReactPlayground: React.FC = () => { - return <> +interface PlaygroundProps { + initFiles: IFiles + initImportMap: IImportMap } -export default ReactPlayground +const Playground: React.FC = ({ initFiles, initImportMap }) => { + const [files, setFiles] = useState(initFiles) + const [selectedFileName, setSelectedFileName] = useState(MAIN_FILE_NAME) + const [importMap, setImportMap] = useState(initImportMap) + + const handleOnChangeFileContent = (content: string, fileName: string, files: IFiles) => { + if (fileName === IMPORT_MAP_FILE_NAME) { + setImportMap(JSON.parse(content)) + } else { + delete files[IMPORT_MAP_FILE_NAME] + setFiles(files) + } + } + + return ( + + setFiles(files)} + onRemoveFile={(_, files) => setFiles(files)} + onRenameFile={(_, __, files) => setFiles(files)} + onChangeFileContent={handleOnChangeFileContent} + onSelectedFileChange={setSelectedFileName} + /> + + + ) +} + +export default Playground diff --git a/src/components/Playground/playground.scss b/src/components/Playground/playground.scss new file mode 100644 index 0000000..35c78d1 --- /dev/null +++ b/src/components/Playground/playground.scss @@ -0,0 +1,18 @@ +[data-component=playground] { + width: 100%; + height: 100%; + + > * { + width: 0 !important; + } + + .playground-error-message { + position: absolute; + bottom: 0; + width: 100%; + color: white; + background-color: #FF4D4FAA; + padding: 5px 10px; + font-size: 1.2em; + } +} \ No newline at end of file diff --git a/src/pages/OnlineEditor.tsx b/src/pages/OnlineEditor.tsx index 248c26b..deebac8 100644 --- a/src/pages/OnlineEditor.tsx +++ b/src/pages/OnlineEditor.tsx @@ -1,30 +1,13 @@ -import React, { useState } from 'react' -import CodeEditor from '@/components/Playground/CodeEditor' -import { IMPORT_MAP_FILE_NAME, initFiles, MAIN_FILE_NAME } from '@/components/Playground/files' -import { IFiles } from '@/components/Playground/shared' +import React from 'react' import FitFullscreen from '@/components/common/FitFullscreen' -import FlexBox from '@/components/common/FlexBox' -import Preview from '@/components/Playground/Preview' +import Playground from '@/components/Playground' +import { initFiles, initImportMap } from '@/components/Playground/files.ts' const OnlineEditor: React.FC = () => { - const [files, setFiles] = useState(initFiles) - const [selectedFileName, setSelectedFileName] = useState(MAIN_FILE_NAME) - return ( <> - - setFiles(files)} - onRemoveFile={(_, files) => setFiles(files)} - onRenameFile={(_, __, files) => setFiles(files)} - onChangeFileContent={(_, __, files) => setFiles(files)} - /> - - + )