diff --git a/beta/src/components/MDX/Sandpack/Console.tsx b/beta/src/components/MDX/Sandpack/Console.tsx new file mode 100644 index 00000000..4b5f31e3 --- /dev/null +++ b/beta/src/components/MDX/Sandpack/Console.tsx @@ -0,0 +1,154 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ +import cn from 'classnames'; +import * as React from 'react'; +import {IconChevron} from 'components/Icon/IconChevron'; + +import {SandpackCodeViewer, useSandpack} from '@codesandbox/sandpack-react'; +import type {SandpackMessageConsoleMethods} from '@codesandbox/sandpack-client'; + +const getType = ( + message: SandpackMessageConsoleMethods +): 'info' | 'warning' | 'error' => { + if (message === 'log' || message === 'info') { + return 'info'; + } + + if (message === 'warn') { + return 'warning'; + } + + return 'error'; +}; + +type ConsoleData = Array<{ + data: Array>; + id: string; + method: SandpackMessageConsoleMethods; +}>; + +const MAX_MESSAGE_COUNT = 100; + +export const SandpackConsole = () => { + const {listen} = useSandpack(); + const [logs, setLogs] = React.useState([]); + const wrapperRef = React.useRef(null); + + React.useEffect(() => { + const unsubscribe = listen((message) => { + if ( + (message.type === 'start' && message.firstLoad) || + message.type === 'refresh' + ) { + setLogs([]); + } + if (message.type === 'console' && message.codesandbox) { + setLogs((prev) => { + const messages = [...prev, ...message.log]; + messages.slice(Math.max(0, messages.length - MAX_MESSAGE_COUNT)); + + return messages.filter(({method}) => method === 'log'); + }); + } + }); + + return unsubscribe; + }, [listen]); + + const [isExpanded, setIsExpanded] = React.useState(false); + + React.useEffect(() => { + if (wrapperRef.current) { + wrapperRef.current.scrollTop = wrapperRef.current.scrollHeight; + } + }, [logs]); + + if (logs.length === 0) { + return null; + } + + return ( +
+
+ + +
+ {isExpanded && ( +
+
+ {logs.map(({data, id, method}) => { + return ( +
+ + {data.map((msg, index) => { + if (typeof msg === 'string') { + return {msg}; + } + + let children; + if (msg != null && typeof msg['@t'] === 'string') { + // CodeSandbox wraps custom types + children = msg['@t']; + } else { + try { + children = JSON.stringify(msg, null, 2); + } catch (error) { + try { + children = Object.prototype.toString.call(msg); + } catch (err) { + children = '[' + typeof msg + ']'; + } + } + } + + return ( + + + + ); + })} + +
+ ); + })} +
+
+ )} +
+ ); +}; diff --git a/beta/src/components/MDX/Sandpack/Preview.tsx b/beta/src/components/MDX/Sandpack/Preview.tsx index 2b33f4e0..fe4ed45e 100644 --- a/beta/src/components/MDX/Sandpack/Preview.tsx +++ b/beta/src/components/MDX/Sandpack/Preview.tsx @@ -7,6 +7,7 @@ import * as React from 'react'; import {useSandpack, LoadingOverlay} from '@codesandbox/sandpack-react'; import cn from 'classnames'; import {Error} from './Error'; +import {SandpackConsole} from './Console'; import type {LintDiagnostic} from './useSandpackLint'; const generateRandomId = (): string => @@ -209,6 +210,7 @@ export function Preview({ loading={!isReady && iframeComputedHeight === null} /> + ); } diff --git a/beta/src/styles/sandpack.css b/beta/src/styles/sandpack.css index ce1af108..bbf2dd75 100644 --- a/beta/src/styles/sandpack.css +++ b/beta/src/styles/sandpack.css @@ -154,6 +154,13 @@ html.dark .sp-code-editor .cm-diagnostic { overflow: auto !important; padding: 18px 0 !important; } + +.console .sp-cm, +.console .sp-cm .cm-scroller, +.console .sp-cm .cm-line { + padding: 0px !important; +} + .sp-cm .cm-gutters { background-color: var(--sp-colors-bg-default); z-index: 1;