From d526d913b90284060ef1df2fb39898c7d2646934 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Mon, 8 Jan 2024 00:16:08 +0800 Subject: [PATCH] Finish editor in Playground Code Editor --- package-lock.json | 46 +++++++++- package.json | 3 + .../ReactPlayground/CodeEditor/Editor/ata.ts | 83 +++++++++++++++++ .../CodeEditor/Editor/editor.scss | 43 +++++++++ .../CodeEditor/Editor/hooks.ts | 72 +++++++++++++++ .../CodeEditor/Editor/index.tsx | 90 +++++++++++++++++++ .../CodeEditor/Editor/monacoConfig.ts | 34 +++++++ src/components/ReactPlayground/Playground.tsx | 6 +- src/components/ReactPlayground/Provider.tsx | 4 +- src/components/ReactPlayground/files.ts | 68 ++++++++++++++ src/components/ReactPlayground/shared.ts | 6 +- .../ReactPlayground/{Utils.ts => utils.ts} | 47 +++++----- src/components/files.ts | 46 ---------- src/pages/OnlineEditor.tsx | 14 ++- 14 files changed, 484 insertions(+), 78 deletions(-) create mode 100644 src/components/ReactPlayground/CodeEditor/Editor/ata.ts create mode 100644 src/components/ReactPlayground/CodeEditor/Editor/editor.scss create mode 100644 src/components/ReactPlayground/CodeEditor/Editor/hooks.ts create mode 100644 src/components/ReactPlayground/CodeEditor/Editor/index.tsx create mode 100644 src/components/ReactPlayground/CodeEditor/Editor/monacoConfig.ts create mode 100644 src/components/ReactPlayground/files.ts rename src/components/ReactPlayground/{Utils.ts => utils.ts} (94%) delete mode 100644 src/components/files.ts diff --git a/package-lock.json b/package-lock.json index e7d4e10..93cb57b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@ant-design/icons": "^5.2.6", "@marsidev/react-turnstile": "^0.4.0", + "@monaco-editor/react": "^4.6.0", + "@typescript/ata": "^0.9.4", "antd": "^5.12.1", "axios": "^1.6.2", "dayjs": "^1.11.10", @@ -22,6 +24,7 @@ "match-sorter": "^6.3.1", "moment": "^2.29.4", "monaco-editor": "^0.45.0", + "monaco-jsx-syntax-highlight": "^1.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.20.1", @@ -1243,6 +1246,30 @@ "react-dom": ">=16.8.0" } }, + "node_modules/@monaco-editor/loader": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@monaco-editor/loader/-/loader-1.4.0.tgz", + "integrity": "sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==", + "dependencies": { + "state-local": "^1.0.6" + }, + "peerDependencies": { + "monaco-editor": ">= 0.21.0 < 1" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/@monaco-editor/react/-/react-4.6.0.tgz", + "integrity": "sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==", + "dependencies": { + "@monaco-editor/loader": "^1.4.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2196,6 +2223,14 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript/ata": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/@typescript/ata/-/ata-0.9.4.tgz", + "integrity": "sha512-PaJ16WouPV/SaA+c0tnOKIqYq24+m93ipl/e0Dkxuianer+ibc5b0/6ZgfCFF8J7QEp57dySMSP9nWOFaCfJnw==", + "peerDependencies": { + "typescript": "^4.4.4" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -5275,6 +5310,11 @@ "resolved": "https://registry.npmmirror.com/monaco-editor/-/monaco-editor-0.45.0.tgz", "integrity": "sha512-mjv1G1ZzfEE3k9HZN0dQ2olMdwIfaeAAjFiwNprLfYNRSz7ctv9XuCT7gPtBGrMUeV1/iZzYKj17Khu1hxoHOA==" }, + "node_modules/monaco-jsx-syntax-highlight": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/monaco-jsx-syntax-highlight/-/monaco-jsx-syntax-highlight-1.2.0.tgz", + "integrity": "sha512-sKzfmNMxLp3Dcwndz0F/EdP3rqe+Z6FbDPazj+/ByNbqoXWkzgb/ocat8h3vpiTwPPiDtSHidT0BFMvLkMtG8A==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz", @@ -7127,6 +7167,11 @@ "dev": true, "peer": true }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" + }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", @@ -7705,7 +7750,6 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 5fc24d9..9f82e44 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "dependencies": { "@ant-design/icons": "^5.2.6", "@marsidev/react-turnstile": "^0.4.0", + "@monaco-editor/react": "^4.6.0", + "@typescript/ata": "^0.9.4", "antd": "^5.12.1", "axios": "^1.6.2", "dayjs": "^1.11.10", @@ -28,6 +30,7 @@ "match-sorter": "^6.3.1", "moment": "^2.29.4", "monaco-editor": "^0.45.0", + "monaco-jsx-syntax-highlight": "^1.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.20.1", diff --git a/src/components/ReactPlayground/CodeEditor/Editor/ata.ts b/src/components/ReactPlayground/CodeEditor/Editor/ata.ts new file mode 100644 index 0000000..370e8c9 --- /dev/null +++ b/src/components/ReactPlayground/CodeEditor/Editor/ata.ts @@ -0,0 +1,83 @@ +import { ATABootstrapConfig, setupTypeAcquisition } from '@typescript/ata' + +type DelegateListener = Required<{ + [k in keyof ATABootstrapConfig['delegate']]: Set> +}> + +const createDelegate = (): DelegateListener => { + return { + receivedFile: new Set(), + progress: new Set(), + errorMessage: new Set(), + finished: new Set(), + started: new Set() + } +} + +const delegateListener = createDelegate() + +type InferSet = T extends Set ? U : never + +export const createATA = async () => { + // @ts-ignore + const ts = await import('https://esm.sh/typescript@5.3.3') + const ata = setupTypeAcquisition({ + projectName: 'monaco-ts', + typescript: ts, + logger: console, + fetcher: (input, init) => { + let result: any + try { + result = fetch(input, init) + } catch (error) { + console.error('Error fetching data:', error) + } + return result + }, + delegate: { + receivedFile: (code, path) => { + delegateListener.receivedFile.forEach((fn) => fn(code, path)) + }, + progress: (downloaded, estimatedTotal) => { + delegateListener.progress.forEach((fn) => fn(downloaded, estimatedTotal)) + }, + started: () => { + delegateListener.started.forEach((fn) => fn()) + }, + finished: (files) => { + delegateListener.finished.forEach((fn) => fn(files)) + } + } + }) + + const acquireType = (code: string) => ata(code) + + const addListener = ( + event: T, + handler: InferSet + ) => { + // @ts-ignore + delegateListener[event].add(handler) + } + + const removeListener = ( + event: T, + handler: InferSet + ) => { + // @ts-ignore + delegateListener[event].delete(handler) + } + + const dispose = () => { + for (const key in delegateListener) { + delegateListener[key as keyof DelegateListener].clear() + } + } + + return { + acquireType, + addListener, + removeListener, + dispose + } +} diff --git a/src/components/ReactPlayground/CodeEditor/Editor/editor.scss b/src/components/ReactPlayground/CodeEditor/Editor/editor.scss new file mode 100644 index 0000000..c72555d --- /dev/null +++ b/src/components/ReactPlayground/CodeEditor/Editor/editor.scss @@ -0,0 +1,43 @@ +.monaco-editor-light { + height: 100%; + overflow: hidden; + background-color: var(--border); + + .jsx-tag-angle-bracket { + color: #800000; + } + + .jsx-text { + color: #000; + } + + .jsx-tag-name { + color: #800000; + } + + .jsx-tag-attribute-key { + color: #f00; + } +} + +.monaco-editor-vs-dark { + height: 100%; + overflow: hidden; + background-color: var(--border); + + .jsx-tag-angle-bracket { + color: #808080; + } + + .jsx-text { + color: #d4d4d4; + } + + .jsx-tag-name { + color: #569cd6; + } + + .jsx-tag-attribute-key { + color: #9cdcfe; + } +} \ No newline at end of file diff --git a/src/components/ReactPlayground/CodeEditor/Editor/hooks.ts b/src/components/ReactPlayground/CodeEditor/Editor/hooks.ts new file mode 100644 index 0000000..2834852 --- /dev/null +++ b/src/components/ReactPlayground/CodeEditor/Editor/hooks.ts @@ -0,0 +1,72 @@ +import { editor, IPosition, Selection } from 'monaco-editor' +import ScrollType = editor.ScrollType +import { Monaco } from '@monaco-editor/react' +import { getWorker, MonacoJsxSyntaxHighlight } from 'monaco-jsx-syntax-highlight' +import { createATA } from '@/components/ReactPlayground/CodeEditor/Editor/ata.ts' + +export const useEditor = () => { + const doOpenEditor = ( + editor: editor.IStandaloneCodeEditor, + input: { options: { selection: Selection } } + ) => { + const selection = input.options ? input.options.selection : null + if (selection) { + if ( + typeof selection.endLineNumber === 'number' && + typeof selection.endColumn === 'number' + ) { + editor.setSelection(selection) + editor.revealRangeInCenter(selection, ScrollType.Immediate) + } else { + const position: IPosition = { + lineNumber: selection.startLineNumber, + column: selection.startColumn + } + editor.setPosition(position) + editor.revealPositionInCenter(position, ScrollType.Immediate) + } + } + } + + const loadJsxSyntaxHighlight = (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => { + const monacoJsxSyntaxHighlight = new MonacoJsxSyntaxHighlight(getWorker(), monaco) + const { highlighter, dispose } = monacoJsxSyntaxHighlight.highlighterBuilder({ editor }) + + editor.onDidChangeModelContent(() => { + highlighter() + }) + highlighter() + + return { highlighter, dispose } + } + + const autoLoadExtraLib = async ( + editor: editor.IStandaloneCodeEditor, + monaco: Monaco, + defaultValue: string, + onWatch: any + ) => { + const typeHelper = await createATA() + + onWatch(typeHelper) + + editor.onDidChangeModelContent(() => { + typeHelper.acquireType(editor.getValue()) + }) + + const addLibraryToRuntime = (code: string, path: string) => { + monaco.languages.typescript.typescriptDefaults.addExtraLib(code, `file://${path}`) + } + + typeHelper.addListener('receivedFile', addLibraryToRuntime) + typeHelper.acquireType(defaultValue) + + return typeHelper + } + + return { + doOpenEditor, + loadJsxSyntaxHighlight, + autoLoadExtraLib + } +} diff --git a/src/components/ReactPlayground/CodeEditor/Editor/index.tsx b/src/components/ReactPlayground/CodeEditor/Editor/index.tsx new file mode 100644 index 0000000..e3d1610 --- /dev/null +++ b/src/components/ReactPlayground/CodeEditor/Editor/index.tsx @@ -0,0 +1,90 @@ +import React from 'react' +import { editor, Selection } from 'monaco-editor' +import MonacoEditor, { Monaco } from '@monaco-editor/react' +import '@/components/ReactPlayground/CodeEditor/Editor/editor.scss' +import { IEditorOptions, IFile, IFiles, ITheme } from '@/components/ReactPlayground/shared' +import { MonacoEditorConfig } from '@/components/ReactPlayground/CodeEditor/Editor/monacoConfig' +import { fileNameToLanguage } from '@/components/ReactPlayground/utils' +import { useEditor } from '@/components/ReactPlayground/CodeEditor/Editor/hooks' + +interface EditorProps { + file: IFile + onChange?: (code: string | undefined) => void + options?: IEditorOptions + theme?: ITheme + files?: IFiles + onJumpFile?: (fileName: string) => void +} + +const Editor: React.FC = ({ file, files, theme, onChange, options, onJumpFile }) => { + const editorRef = useRef() + const { doOpenEditor, loadJsxSyntaxHighlight } = useEditor() + const jsxSyntaxHighlightRef = useRef<{ + highlighter: (code?: string | undefined) => void + dispose: () => void + }>({ + highlighter: () => undefined, + dispose: () => undefined + }) + + const handleOnEditorDidMount = (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => { + editorRef.current = editor + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { + void editor.getAction('editor.action.formatDocument')?.run() + }) + + monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ + jsx: monaco.languages.typescript.JsxEmit.Preserve, + esModuleInterop: true + }) + + files && + Object.entries(files).forEach(([key]) => { + if (!monaco.editor.getModel(monaco.Uri.parse(`file:///${key}`))) { + monaco.editor.createModel( + files[key].value, + fileNameToLanguage(key), + monaco.Uri.parse(`file:///${key}`) + ) + } + }) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + editor['_codeEditorService'].doOpenEditor = function ( + editor: editor.IStandaloneCodeEditor, + input: { options: { selection: Selection }; resource: { path: string } } + ) { + const path = input.resource.path + if (!path.startsWith('/node_modules/')) { + onJumpFile?.(path.replace('/', '')) + doOpenEditor(editor, input) + } + } + + jsxSyntaxHighlightRef.current = loadJsxSyntaxHighlight(editor, monaco) + } + + useEffect(() => { + editorRef.current?.focus() + jsxSyntaxHighlightRef?.current?.highlighter?.() + }, [file.name]) + + return ( + <> + + + ) +} + +export default Editor diff --git a/src/components/ReactPlayground/CodeEditor/Editor/monacoConfig.ts b/src/components/ReactPlayground/CodeEditor/Editor/monacoConfig.ts new file mode 100644 index 0000000..20f570a --- /dev/null +++ b/src/components/ReactPlayground/CodeEditor/Editor/monacoConfig.ts @@ -0,0 +1,34 @@ +import { editor } from 'monaco-editor' + +export const MonacoEditorConfig: editor.IStandaloneEditorConstructionOptions = { + automaticLayout: true, + cursorBlinking: 'smooth', + fontLigatures: true, + formatOnPaste: true, + formatOnType: true, + fontSize: 14, + showDeprecated: true, + showUnused: true, + showFoldingControls: 'mouseover', + scrollBeyondLastLine: false, + minimap: { + enabled: false, + }, + inlineSuggest: { + enabled: false, + }, + fixedOverflowWidgets: true, + smoothScrolling: true, + smartSelect: { + selectSubwords: true, + selectLeadingAndTrailingWhitespace: true, + }, + tabSize: 2, + overviewRulerBorder: false, // 不要滚动条的边框 + // 滚动条设置 + scrollbar: { + verticalScrollbarSize: 6, // 竖滚动条 + horizontalScrollbarSize: 6, // 横滚动条 + }, + // lineNumbers: 'off', // 隐藏控制行号 +} diff --git a/src/components/ReactPlayground/Playground.tsx b/src/components/ReactPlayground/Playground.tsx index ecb7d82..12780bb 100644 --- a/src/components/ReactPlayground/Playground.tsx +++ b/src/components/ReactPlayground/Playground.tsx @@ -1,3 +1,4 @@ +/* import React from 'react' import { IPlayground } from '@/components/ReactPlayground/shared.ts' import { PlaygroundContext } from '@/components/ReactPlayground/Provider.tsx' @@ -6,7 +7,7 @@ import { getCustomActiveFile, getMergedCustomFiles, getPlaygroundTheme -} from '@/components/ReactPlayground/Utils.ts' +} from '@/components/ReactPlayground/utils.ts' const defaultCodeSandboxOptions = { theme: 'dark', @@ -67,7 +68,8 @@ const Playground: React.FC = (props) => { } }, []) - return files[ENTRY_FILE_NAME] ? <> : undefined + return files[ENTRY_FILE_NAME] ?
: undefined } export default Playground +*/ diff --git a/src/components/ReactPlayground/Provider.tsx b/src/components/ReactPlayground/Provider.tsx index 31f6370..71b11e5 100644 --- a/src/components/ReactPlayground/Provider.tsx +++ b/src/components/ReactPlayground/Provider.tsx @@ -1,11 +1,11 @@ import React from 'react' import { IFiles, IPlaygroundContext, ITheme } from '@/components/ReactPlayground/shared.ts' -import { MAIN_FILE_NAME } from '@/components/files.ts' +import { MAIN_FILE_NAME } from '@/components/ReactPlayground/files.ts' import { fileNameToLanguage, setPlaygroundTheme, strToBase64 -} from '@/components/ReactPlayground/Utils.ts' +} from '@/components/ReactPlayground/utils.ts' const initialContext: Partial = { selectedFileName: MAIN_FILE_NAME diff --git a/src/components/ReactPlayground/files.ts b/src/components/ReactPlayground/files.ts new file mode 100644 index 0000000..338df8d --- /dev/null +++ b/src/components/ReactPlayground/files.ts @@ -0,0 +1,68 @@ +import importMap from '@/components/ReactPlayground/template/import-map.json?raw' +import AppCss from '@/components/ReactPlayground/template/src/App.css?raw' +import App from '@/components/ReactPlayground/template/src/App.tsx?raw' +import main from '@/components/ReactPlayground/template/src/main.tsx?raw' +import { IFiles } from '@/components/ReactPlayground/shared' +import { base64ToStr } from '@/components/ReactPlayground/utils.ts' + +export const MAIN_FILE_NAME = 'App.tsx' +export const IMPORT_MAP_FILE_NAME = 'import-map.json' +export const ENTRY_FILE_NAME = 'main.tsx' + +const fileNameToLanguage = (name: string) => { + 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' + return 'javascript' +} + +const getFilesFromUrl = () => { + let files: IFiles | undefined + try { + if (typeof window !== 'undefined') { + const hash = window.location.hash + if (hash) files = JSON.parse(base64ToStr(hash?.split('#')[1])) + } + } catch (error) { + console.error(error) + } + return files +} + +export const initFiles: IFiles = getFilesFromUrl() || { + [ENTRY_FILE_NAME]: { + name: ENTRY_FILE_NAME, + language: fileNameToLanguage(ENTRY_FILE_NAME), + value: main + }, + [MAIN_FILE_NAME]: { + name: MAIN_FILE_NAME, + language: fileNameToLanguage(MAIN_FILE_NAME), + value: App + }, + 'App.css': { + 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 reactTemplateFiles = { + [ENTRY_FILE_NAME]: { + name: ENTRY_FILE_NAME, + language: fileNameToLanguage(ENTRY_FILE_NAME), + value: main + }, + [IMPORT_MAP_FILE_NAME]: { + name: IMPORT_MAP_FILE_NAME, + language: fileNameToLanguage(IMPORT_MAP_FILE_NAME), + value: importMap + } +} diff --git a/src/components/ReactPlayground/shared.ts b/src/components/ReactPlayground/shared.ts index 3fa42c1..bbdf821 100644 --- a/src/components/ReactPlayground/shared.ts +++ b/src/components/ReactPlayground/shared.ts @@ -4,7 +4,7 @@ import { editor } from 'monaco-editor' export interface IFile { name: string value: string - language: string + language: 'javascript' | 'typescript' | 'json' | 'css' active?: boolean hidden?: boolean } @@ -13,7 +13,7 @@ export interface IFiles { [key: string]: IFile } -export type ITheme = 'light' | 'dark' +export type ITheme = 'light' | 'vs-dark' export type IImportMap = { imports: Record } @@ -26,7 +26,7 @@ export interface ICustomFiles { hidden?: boolean } } -export type IEditorOptions = editor.IStandaloneEditorConstructionOptions & any +export type IEditorOptions = editor.IStandaloneEditorConstructionOptions export interface IEditorContainer { showFileSelector?: boolean fileSelectorReadOnly?: boolean diff --git a/src/components/ReactPlayground/Utils.ts b/src/components/ReactPlayground/utils.ts similarity index 94% rename from src/components/ReactPlayground/Utils.ts rename to src/components/ReactPlayground/utils.ts index 77f4df4..0569a37 100644 --- a/src/components/ReactPlayground/Utils.ts +++ b/src/components/ReactPlayground/utils.ts @@ -1,6 +1,6 @@ -import { ICustomFiles, IFiles, IImportMap, ITheme } from '@/components/ReactPlayground/shared.ts' import { strFromU8, strToU8, unzlibSync, zlibSync } from 'fflate' -import { IMPORT_MAP_FILE_NAME, reactTemplateFiles } from '@/components/files.ts' +import { ICustomFiles, IFiles, IImportMap, ITheme } from '@/components/ReactPlayground/shared' +import { IMPORT_MAP_FILE_NAME, reactTemplateFiles } from '@/components/ReactPlayground/files' export const strToBase64 = (str: string) => { const buffer = strToU8(str) @@ -22,19 +22,10 @@ export const base64ToStr = (base64: string) => { return '' } -export const fileNameToLanguage = (name: string) => { - 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' - return 'javascript' -} - const STORAGE_DARK_THEME = 'react-playground-prefer-dark' export const setPlaygroundTheme = (theme: ITheme) => { - localStorage.setItem(STORAGE_DARK_THEME, String(theme === 'dark')) + localStorage.setItem(STORAGE_DARK_THEME, String(theme === 'vs-dark')) document .querySelectorAll('div[data-id="react-playground"]') ?.forEach((item) => item.setAttribute('class', theme)) @@ -42,7 +33,7 @@ export const setPlaygroundTheme = (theme: ITheme) => { export const getPlaygroundTheme = () => { const isDarkTheme = JSON.parse(localStorage.getItem(STORAGE_DARK_THEME) || 'false') - return isDarkTheme ? 'dark' : 'light' + return isDarkTheme ? 'vs-dark' : 'light' } const transformCustomFiles = (files: ICustomFiles) => { @@ -69,6 +60,17 @@ const transformCustomFiles = (files: ICustomFiles) => { return newFiles } +export const getCustomActiveFile = (files?: ICustomFiles) => { + if (!files) return null + return Object.keys(files).find((key) => { + const tempFile = files[key] + if (typeof tempFile !== 'string' && tempFile.active) { + return key + } + return null + }) +} + export const getMergedCustomFiles = (files?: ICustomFiles, importMap?: IImportMap) => { if (!files) return null if (importMap) { @@ -89,16 +91,6 @@ export const getMergedCustomFiles = (files?: ICustomFiles, importMap?: IImportMa } } -export const getCustomActiveFile = (files?: ICustomFiles) => { - if (!files) return null - return Object.keys(files).find((key) => { - const tempFile = files[key] - if (typeof tempFile !== 'string' && tempFile.active) { - return key - } - return null - }) -} export const getFilesFromUrl = () => { let files: IFiles | undefined try { @@ -111,3 +103,12 @@ export const getFilesFromUrl = () => { } return files } + +export const fileNameToLanguage = (name: string) => { + 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' + return 'javascript' +} diff --git a/src/components/files.ts b/src/components/files.ts deleted file mode 100644 index 07b762b..0000000 --- a/src/components/files.ts +++ /dev/null @@ -1,46 +0,0 @@ -import importMap from './template/import-map.json?raw' -import AppCss from './template/src/App.css?raw' -import App from './template/src/App.tsx?raw' -import main from './template/src/main.tsx?raw' -import { IFiles } from '@/components/ReactPlayground/shared.ts' -import { fileNameToLanguage } from '@/components/ReactPlayground/Utils.ts' - -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 initFiles: IFiles = getFilesFromUrl() || { - [ENTRY_FILE_NAME]: { - name: ENTRY_FILE_NAME, - language: fileNameToLanguage(ENTRY_FILE_NAME), - value: main - }, - [MAIN_FILE_NAME]: { - name: MAIN_FILE_NAME, - language: fileNameToLanguage(MAIN_FILE_NAME), - value: App - }, - 'App.css': { - 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 reactTemplateFiles = { - [ENTRY_FILE_NAME]: { - name: ENTRY_FILE_NAME, - language: fileNameToLanguage(ENTRY_FILE_NAME), - value: main - }, - [IMPORT_MAP_FILE_NAME]: { - name: IMPORT_MAP_FILE_NAME, - language: fileNameToLanguage(IMPORT_MAP_FILE_NAME), - value: importMap - } -} diff --git a/src/pages/OnlineEditor.tsx b/src/pages/OnlineEditor.tsx index 7d67ca3..04293c8 100644 --- a/src/pages/OnlineEditor.tsx +++ b/src/pages/OnlineEditor.tsx @@ -1,7 +1,19 @@ import React from 'react' +import Editor from '@/components/ReactPlayground/CodeEditor/Editor' const OnlineEditor: React.FC = () => { - return <> + return ( + <> + {}' + }} + /> + + ) } export default OnlineEditor