/* eslint no-underscore-dangle: 0 */ import React, { useRef, useEffect, useState, Suspense, lazy } from 'react'; import { useStaticQuery, graphql, Link } from 'gatsby'; import classNames from 'classnames'; import { Skeleton, Result, Layout, Space, PageHeader, Divider, TreeSelect, Tooltip, } from 'antd'; import { useMedia } from 'react-use'; import debounce from 'lodash/debounce'; import { filter } from 'lodash-es'; import { LeftOutlined, EditOutlined } from '@ant-design/icons'; import { useTranslation, withTranslation, WithTranslation, } from 'react-i18next'; import SplitPane from 'react-split-pane'; import { transform } from '@babel/standalone'; import { splitPaneMap } from '../layoutConfig'; import Toolbar, { EDITOR_TABS } from './Toolbar'; import ChartViewSwitcher from './ChartViewSwitcher'; import LayoutSwitcher from './LayoutSwitcher'; import PlayGrounds, { PlayGroundItemProps } from './PlayGrounds'; import APIDoc from './APIDoc'; import PageLoading from './PageLoading'; import styles from './PlayGround.module.less'; import { getGithubSourceUrl } from '../templates/document'; const { Content, Sider } = Layout; interface PlayGroundProps { exampleSections: any; location: Location; description: string; markdownRemark: any; categories: string[]; allDemos: any; } interface TreeItem { title: string; value: string; children?: any; } const MonacoEditor = lazy(() => import('react-monaco-editor')); const execute = debounce( ( code: string, node: HTMLDivElement, exampleContainer: string | undefined, ) => { const script = document.createElement('script'); script.innerHTML = ` try { ${code} } catch(e) { if (window.__reportErrorInPlayGround) { window.__reportErrorInPlayGround(e); } } `; // eslint-disable-next-line no-param-reassign node.innerHTML = exampleContainer || '
'; node!.appendChild(script); }, 300, ); const PlayGround: React.FC = ({ exampleSections, location, markdownRemark, description, allDemos, categories, }) => { const { site } = useStaticQuery( graphql` query { site { siteMetadata { showChartResize showAPIDoc githubUrl docGitUrl playground { container playgroundDidMount playgroundWillUnmount dependencies htmlCodeTemplate } } } } `, ); const { siteMetadata: { githubUrl, playground, docGitUrl }, } = site; const localLayout = typeof window !== 'undefined' ? localStorage.getItem('layout') : null; const { showChartResize, showAPIDoc } = site.siteMetadata; const [layout, updateLayout] = useState(localLayout || 'viewDefault'); const [codeQuery, updateCodeQuery] = useState(''); const { i18n, t } = useTranslation(); const [currentExample, updateCurrentExample] = useState< PlayGroundItemProps >(); const [editRef, updateEditRef] = useState(); const { examples } = exampleSections; const playgroundNode = useRef(null); const [error, setError] = useState(); const [collapsed, updateCollapsed] = useState(false); const [showAPISearch, updateShowAPIsearch] = useState(true); const [compiledCode, updateCompiledCode] = useState(''); const [currentCategory, updateCurrentCategory] = useState(''); const [relativePath, updateRelativePath] = useState(''); const [fileExtension, updateFileExtension] = useState(''); const [title, updateTitle] = useState(''); const [view, updateView] = useState('desktop'); const [docsEmpty, updateDocsEmpty] = useState(false); const [currentSourceCode, updateCurrentSourceCode] = useState(''); const [currentSourceData, updateCurrentSourceData] = useState(null); const editroRef = useRef(null); const isWide = useMedia('(min-width: 767.99px)', true); 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); }; if (typeof window !== 'undefined') { (window as any).__reportErrorInPlayGround = (e: Error) => { console.error(e); // eslint-disable-line no-console setError(e); }; } const updateCurrentExampleParams = (current: PlayGroundItemProps) => { if (!current?.relativePath) return; updateRelativePath(current?.relativePath); updateFileExtension( current?.relativePath.split('.')[ current.relativePath.split('.').length - 1 ] || 'js', ); updateTitle(current?.title); updateCompiledCode(current.babeledSource); updateCurrentSourceCode(replaceInsertCss(current.source)); }; const setLayout = (ifWide: boolean, empty: boolean) => { if (!ifWide) { updateLayout('viewTwoRows'); updateCollapsed(true); } else if (!showAPIDoc || empty) { updateLayout('viewTwoCols'); } }; useEffect(() => { if (currentExample || !examples) return; const defaultExample = examples.find( (item: any) => `#${item.filename.split('.')[0]}` === location.hash, ) || examples[0]; updateCurrentExample(defaultExample); if ( !exampleSections?.design?.node?.html && !description && !exampleSections?.API?.node?.html ) { updateDocsEmpty(true); } }, [examples, description]); useEffect(() => { setLayout(isWide, docsEmpty); }, [isWide, docsEmpty]); useEffect(() => { if (!currentExample || !allDemos) return; updateView('desktop'); updateCurrentExampleParams(currentExample); filter(allDemos, (item: any, key: string) => { const cur = item.find( (val: any) => val?.relativePath === currentExample.relativePath, ); if (cur) updateCurrentCategory(key); return item; }); }, [currentExample, allDemos]); const executeCode = () => { if (!compiledCode || !playgroundNode || !playgroundNode.current) { return; } execute(compiledCode, playgroundNode.current, playground?.container); }; useEffect(() => { executeCode(); }, [compiledCode, error, view]); 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, updateEditroTabs] = useState([]); const [currentEditorTab, updateCurrentEditorTab] = useState( EDITOR_TABS.JAVASCRIPT, ); useEffect(() => { const dataFileMatch = currentSourceCode.match(/fetch\('(.*)'\)/); if (dataFileMatch && dataFileMatch.length > 0) { updateEditroTabs([EDITOR_TABS.JAVASCRIPT, EDITOR_TABS.DATA]); fetch(dataFileMatch[1]) .then((response) => response.json()) .then((data) => { updateCurrentSourceData(data); }); } }, [currentSourceCode]); 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 codeEditor = ( onCodeChange(value)} editorWillMount={(monaco: any) => { monaco.editor.defineTheme('customTheme', { base: 'vs', inherit: true, rules: [], colors: { 'editor.inactiveSelectionBackground': '#ffffff', }, }); monaco.editor.setTheme('customTheme'); }} editorDidMount={(editor, monaco) => { updateEditRef(editor); editor.addAction({ // An unique identifier of the contributed action. id: 'search-in-doc', // A label of the action that will be presented to the user. label: 'search in document', contextMenuGroupId: 'navigation', // An optional array of keybindings for the action. keybindings: [ // eslint-disable-next-line no-bitwise monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10, ], contextMenuOrder: 0, run: (ed: any) => { const val = ed.getModel().getValueInRange(ed.getSelection()); updateCodeQuery(val); }, }); editroRef.current = editor.getModel(); }} /> ); const dispatchResizeEvent = () => { const e = new Event('resize'); window.dispatchEvent(e); }; const toggle = () => { updateCollapsed(!collapsed); }; useEffect(() => { dispatchResizeEvent(); if (isWide && showAPIDoc && !docsEmpty) localStorage.setItem('layout', layout); const pane = document.getElementsByClassName('ant-layout'); if (!pane[1]) return; if (layout === 'viewTwoRows') { pane[1].setAttribute('style', 'margin-top: 64px'); } else { pane[1].setAttribute('style', 'margin-top: 0'); } }, [layout, collapsed]); // 图例滚动到当前节点 useEffect(() => { if (!currentExample || !currentExample?.filename || !layout) { return; } const id = `example-${currentExample?.filename?.split('.')[0]}`; const cardNode = document.getElementById(id); if (cardNode) { cardNode.scrollIntoView({ behavior: 'smooth', block: 'nearest', }); } }, [currentExample, layout, playground]); const getThemeCode = (type: string, themeString: string) => { const colors = JSON.parse(themeString); let res; if (type === 'g2') { res = { styleSheet: { brandColor: colors.colors10[0], paletteQualitative10: colors.colors10, paletteQualitative20: colors.colors20, }, }; } else { res = { theme: { styleSheet: { brandColor: colors.colors10[0], paletteQualitative10: colors.colors10, paletteQualitative20: colors.colors20, }, }, }; } return JSON.stringify(res); }; // 根据pane框度及当前视图判断是否需要展示API文档搜索框 const calcShowSearch = (size: number) => { const clientw = document.body.clientWidth; if (size / clientw > 0.668) { updateShowAPIsearch(false); } else { updateShowAPIsearch(true); } }; const routes = [ { path: `/${i18n.language}/examples`, breadcrumbName: i18n.language === 'zh' ? '图表示例' : 'Gallery', }, { path: '/category', breadcrumbName: 'Second-level Menu', children: [], }, ]; const getTreeData = () => { const result: TreeItem[] = []; categories.forEach((category: string) => { const root: TreeItem = { title: category, value: '', children: [], }; allDemos[category].forEach((item: any, index: number) => { const demoSlug = item.relativePath.replace( /\/demo\/(.*)\..*/, (_: string, filename: string) => { return `#${filename}`; }, ); const path = `/${i18n.language}/examples/${demoSlug}`; if (index === 0) { root.value = `root::${path}`; } const child = { title: typeof item.title === 'object' ? item.title[i18n.language] : item.title || item?.filename, value: path, }; root.children.push(child); }); result.push(root); }); return result; }; const onChange = (value: string) => { const tmp = value.split('::'); const path = tmp[1] ? tmp[1] : value; // window.location.assign(path); // window.location.href = `${path}`; window.history.pushState({}, '', `${path}`); window.location.reload(); }; function itemRender(route: any) { if (route.children) { return ( ); } return ( {route.breadcrumbName} ); } return ( calcShowSearch(size)} > {playground && currentExample && layout ? ( {relativePath ? ( } extra={ }> {showChartResize && layout === 'viewDefault' && ( )} {showAPIDoc && !docsEmpty && layout !== 'viewTwoRows' && ( )} } />
{error ? ( {error && error.message}} /> ) : (
)}
) : ( )}
{title && fileExtension && ( )} {!relativePath ? ( ) : (
}>{codeEditor}
)}
) : ( <> )} {relativePath && (layout === 'viewDefault' || layout === 'viewThreeCols') ? ( ) : ( <> )} ); }; class ErrorHandlerPlayGround extends React.Component< PlayGroundProps & 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()(ErrorHandlerPlayGround);