/* eslint no-underscore-dangle: 0 */ import React, { useRef, useEffect, useState } from 'react'; // gatsby ssr not support Suspense&lazy https://github.com/gatsbyjs/gatsby/issues/11960 import loadable from '@loadable/component'; import { useStaticQuery, graphql } from 'gatsby'; import { useDebounce } from 'react-use'; import classNames from 'classnames'; import { Result } from 'antd'; import { useTranslation, withTranslation, WithTranslation, } from 'react-i18next'; import { transform } from '@babel/standalone'; import SplitPane from 'react-split-pane'; import Toolbar, { EDITOR_TABS } from './Toolbar'; import styles from './MdPlayGround.module.less'; const MonacoEditor = loadable(() => import('react-monaco-editor')); interface PlayGroundProps { source: string; babeledSource: string; relativePath?: string; title?: string; location?: Location; playground?: { container?: string; playgroundDidMount?: string; playgroundWillUnmount?: string; dependencies?: { [key: string]: string; }; htmlCodeTemplate?: string; }; height?: number; replaceId?: string; } const PlayGround: React.FC = ({ source, babeledSource, relativePath = '', playground = {}, location, title = '', height, replaceId = 'container', }) => { const { site } = useStaticQuery( graphql` query { site { siteMetadata { mdPlayground { splitPaneMainSize } } } } `, ); const { siteMetadata: { mdPlayground: { splitPaneMainSize }, }, } = site; console.log(splitPaneMainSize); const splitPaneSize = splitPaneMainSize || '62%'; const { t, i18n } = useTranslation(); const playgroundNode = useRef(null); const [error, setError] = useState(); const [compiledCode, updateCompiledCode] = useState(babeledSource); const [currentSourceData, updateCurrentSourceData] = useState(null); const editroRef = useRef(null); const comment = i18n.language === 'zh' ? `// 我们用 insert-css 演示引入自定义样式 // 推荐将样式添加到自己的样式文件中 // 若拷贝官方代码,别忘了 npm install insert-css insertCss(` : `// We use 'insert-css' to insert custom styles // It is recommended to add the style to your own style sheet files // If you want to copy the code directly, please remember to install the npm package 'insert-css insertCss(`; const replaceInsertCss = (str: string) => { // 统一增加对 insert-css 的使用注释 return str.replace(/^insertCss\(/gm, comment); }; const [currentSourceCode, updateCurrentSourceCode] = useState( replaceInsertCss(source), ); if (typeof window !== 'undefined') { (window as any).__reportErrorInPlayGround = (e: Error) => { console.error(e); // eslint-disable-line no-console setError(e); }; } const fullscreenNode = useRef(null); const [isFullScreen, updateIsFullScreen] = useState(false); const toggleFullscreen = () => { updateIsFullScreen(!isFullScreen); if (fullscreenNode.current) { if (!isFullScreen && !document.fullscreenElement) { fullscreenNode.current.requestFullscreen(); } else if (document.exitFullscreen) { document.exitFullscreen(); } } }; const execute = ( code: string, node: HTMLDivElement, exampleContainer: string | undefined, ) => { const script = document.createElement('script'); // replace container id in case of multi demos in document const newCode = code.replace(/'container'|"container"/, `'${replaceId}'`); script.innerHTML = ` try { ${newCode} } catch(e) { if (window.__reportErrorInPlayGround) { window.__reportErrorInPlayGround(e); } } `; // eslint-disable-next-line no-param-reassign node.innerHTML = exampleContainer || `
`; node!.appendChild(script); console.log(1); }; const executeCode = () => { if (!compiledCode || !playgroundNode || !playgroundNode.current) { return; } execute(compiledCode, playgroundNode.current, playground.container); }; useDebounce( () => { executeCode(); }, 1000, [compiledCode, error], ); useEffect(() => { if (playground.playgroundDidMount) { // eslint-disable-next-line no-new-func new Function(playground.playgroundDidMount)(); } return () => { if (playground.playgroundWillUnmount) { // eslint-disable-next-line no-new-func new Function(playground.playgroundWillUnmount)(); } }; }, []); const [editorTabs, updateEditorTabs] = useState([]); const [currentEditorTab, updateCurrentEditorTab] = useState( EDITOR_TABS.JAVASCRIPT, ); useEffect(() => { const dataFileMatch = currentSourceCode.match(/fetch\('(.*)'\)/); if (dataFileMatch && dataFileMatch.length > 0) { fetch(dataFileMatch[1]) .then((response) => response.json()) .then((data) => { updateEditorTabs([EDITOR_TABS.JAVASCRIPT, EDITOR_TABS.DATA]); updateCurrentSourceData(data); }); } }, []); const onCodeChange = (value: string) => { if (currentEditorTab === EDITOR_TABS.JAVASCRIPT) { updateCurrentSourceCode(value); try { const { code } = transform(value, { filename: relativePath, presets: ['react', 'typescript', 'es2015', 'stage-3'], plugins: ['transform-modules-umd'], }); updateCompiledCode(code); } catch (e) { console.error(e); // eslint-disable-line no-console setError(e); return; } setError(null); } }; useEffect(() => { if (editroRef.current) { if (currentEditorTab === EDITOR_TABS.JAVASCRIPT) { editroRef.current.setValue(currentSourceCode); } else if (currentEditorTab === EDITOR_TABS.DATA) { editroRef.current.setValue(JSON.stringify(currentSourceData, null, 2)); } } }, [currentEditorTab]); const editor = ( onCodeChange(value)} editorWillMount={(monaco) => { monaco.editor.defineTheme('customTheme', { base: 'vs', inherit: true, rules: [], colors: { 'editor.inactiveSelectionBackground': '#ffffff', }, }); monaco.editor.setTheme('customTheme'); }} editorDidMount={(editorInstance) => { editroRef.current = editorInstance.getModel(); }} /> ); const fileExtension = relativePath.split('.')[relativePath.split('.').length - 1] || 'js'; const dispatchResizeEvent = () => { const e = new Event('resize'); window.dispatchEvent(e); }; return (
{error ? ( {error && error.message}} /> ) : (
)}
{editor}
); }; interface MdPlaygroundProps { examples: PlayGroundProps[]; path: string; height?: number; rid?: string; } const MdPlayGround: React.FC = ({ examples, path, rid, height = 400, }) => { if (!examples || !examples.length || !path) return null; const example = examples.find((item: any) => item.relativePath === path); if (!example) return null; return ( ); }; class ErrorHandlerMdPlayGround extends React.Component< MdPlaygroundProps & WithTranslation, { error?: Error } > { state = { error: undefined, }; static getDerivedStateFromError(error: Error) { // 更新 state 使下一次渲染能够显示降级后的 UI return { error }; } render() { const { t } = this.props; const { error } = this.state; if (error) { // 你可以自定义降级后的 UI 并渲染 return ( {error && (error as any).message}} /> ); } return ; } } export default withTranslation()(ErrorHandlerMdPlayGround);