diff --git a/sandpack-react/src/components/Console/Console.stories.tsx b/sandpack-react/src/components/Console/Console.stories.tsx index 8faca252d..598334e76 100644 --- a/sandpack-react/src/components/Console/Console.stories.tsx +++ b/sandpack-react/src/components/Console/Console.stories.tsx @@ -3,7 +3,7 @@ import React from "react"; import { SandpackCodeEditor, SandpackPreview } from ".."; import { SandpackProvider, SandpackLayout, Sandpack } from "../.."; -import { SandpackConsole } from "./SandpackConsole"; +import { SandpackConsole, SandpackConsoleRef } from "./SandpackConsole"; export default { title: "components/Console", @@ -130,3 +130,20 @@ export const Preset: React.FC = () => { ); }; + +export const ImperativeReset: React.FC = () => { + const consoleRef = React.useRef(null); + + const resetLogs = () => { + consoleRef.current?.reset(); + }; + + return ( + + + + + + + ); +}; diff --git a/sandpack-react/src/components/Console/SandpackConsole.tsx b/sandpack-react/src/components/Console/SandpackConsole.tsx index f620aae7e..87ca96064 100644 --- a/sandpack-react/src/components/Console/SandpackConsole.tsx +++ b/sandpack-react/src/components/Console/SandpackConsole.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { useSandpack, useSandpackClient, useSandpackShell } from "../.."; +import { useSandpack, useSandpackShell } from "../.."; import { useSandpackShellStdout } from "../../hooks/useSandpackShellStdout"; import { css, THEME_PREFIX } from "../../styles"; import { classNames } from "../../utils/classNames"; @@ -22,6 +22,11 @@ interface SandpackConsoleProps { showSetupProgress?: boolean; maxMessageCount?: number; onLogsChange?: (logs: SandpackConsoleData) => void; + resetOnPreviewRestart?: boolean; +} + +export interface SandpackConsoleRef { + reset: () => void; } /** @@ -30,122 +35,138 @@ interface SandpackConsoleProps { * a light version of a browser console, which means that it's * limited to a set of common use cases you may encounter when coding. */ -export const SandpackConsole: React.FC< +export const SandpackConsole = React.forwardRef< + SandpackConsoleRef, React.HTMLAttributes & SandpackConsoleProps -> = ({ - showHeader = true, - showSyntaxError = false, - maxMessageCount, - onLogsChange, - className, - showSetupProgress = false, - ...props -}) => { - const { - sandpack: { environment }, - } = useSandpack(); - - const { restart } = useSandpackShell(); - - const [currentTab, setCurrentTab] = React.useState<"server" | "client">( - environment === "node" ? "server" : "client" - ); - - const { logs: consoleData, reset: resetConsole } = useSandpackConsole({ - maxMessageCount, - showSyntaxError, - }); - - const { logs: stdoutData, reset: resetStdout } = useSandpackShellStdout({ - maxMessageCount, - }); - - const wrapperRef = React.useRef(null); - - React.useEffect(() => { - onLogsChange?.(consoleData); - - if (wrapperRef.current) { - wrapperRef.current.scrollTop = wrapperRef.current.scrollHeight; - } - }, [onLogsChange, consoleData, stdoutData, currentTab]); - - const isServerTab = currentTab === "server"; - const isNodeEnvironment = environment === "node"; - - return ( - - {showSetupProgress && ( - - )} - - {(showHeader || isNodeEnvironment) && ( -
- )} - -
( + ( + { + showHeader = true, + showSyntaxError = false, + maxMessageCount, + onLogsChange, + className, + showSetupProgress = false, + resetOnPreviewRestart = false, + ...props + }, + ref + ) => { + const { + sandpack: { environment }, + } = useSandpack(); + + const { restart } = useSandpackShell(); + + const [currentTab, setCurrentTab] = React.useState<"server" | "client">( + environment === "node" ? "server" : "client" + ); + + const { logs: consoleData, reset: resetConsole } = useSandpackConsole({ + maxMessageCount, + showSyntaxError, + resetOnPreviewRestart, + }); + + const { logs: stdoutData, reset: resetStdout } = useSandpackShellStdout({ + maxMessageCount, + resetOnPreviewRestart, + }); + + const wrapperRef = React.useRef(null); + + React.useEffect(() => { + onLogsChange?.(consoleData); + + if (wrapperRef.current) { + wrapperRef.current.scrollTop = wrapperRef.current.scrollHeight; + } + }, [onLogsChange, consoleData, stdoutData, currentTab]); + + const isServerTab = currentTab === "server"; + const isNodeEnvironment = environment === "node"; + + React.useImperativeHandle(ref, () => ({ + reset() { + resetConsole(); + resetStdout(); + }, + })); + + return ( + - {isServerTab ? ( - - ) : ( - + {showSetupProgress && ( + )} -
-
)} - > - {isServerTab && ( + +
+ {isServerTab ? ( + + ) : ( + + )} +
+ +
+ {isServerTab && ( + { + restart(); + resetConsole(); + resetStdout(); + }} + > + + + )} + { - restart(); - resetConsole(); - resetStdout(); + if (currentTab === "client") { + resetConsole(); + } else { + resetStdout(); + } }} > - + - )} - - { - if (currentTab === "client") { - resetConsole(); - } else { - resetStdout(); - } - }} - > - - -
- - ); -}; +
+ + ); + } +); diff --git a/sandpack-react/src/components/Console/useSandpackConsole.ts b/sandpack-react/src/components/Console/useSandpackConsole.ts index af9e32612..f53dfc7aa 100644 --- a/sandpack-react/src/components/Console/useSandpackConsole.ts +++ b/sandpack-react/src/components/Console/useSandpackConsole.ts @@ -14,21 +14,25 @@ import type { SandpackConsoleData } from "./utils/getType"; * * @category Hooks */ -export const useSandpackConsole = (props?: { +export const useSandpackConsole = ({ + clientId, + maxMessageCount = MAX_MESSAGE_COUNT, + showSyntaxError = false, + resetOnPreviewRestart = false, +}: { clientId?: string; maxMessageCount?: number; showSyntaxError?: boolean; + resetOnPreviewRestart: boolean; }): { logs: SandpackConsoleData; reset: () => void } => { const [logs, setLogs] = React.useState([]); const { listen } = useSandpack(); - const showSyntaxError = props?.showSyntaxError ?? false; - const maxMessageCount = props?.maxMessageCount ?? MAX_MESSAGE_COUNT; - const clientId = props?.clientId; - React.useEffect(() => { const unsubscribe = listen((message) => { - if (message.type === "console" && message.codesandbox) { + if (resetOnPreviewRestart && message.type === "start") { + setLogs([]); + } else if (message.type === "console" && message.codesandbox) { const payloadLog = Array.isArray(message.log) ? message.log : [message.log]; diff --git a/sandpack-react/src/hooks/useSandpackShellStdout.ts b/sandpack-react/src/hooks/useSandpackShellStdout.ts index 904c2c9f7..a716fcbc1 100644 --- a/sandpack-react/src/hooks/useSandpackShellStdout.ts +++ b/sandpack-react/src/hooks/useSandpackShellStdout.ts @@ -6,9 +6,14 @@ import { useSandpack } from "."; const MAX_MESSAGE_COUNT = 400 * 2; -export const useSandpackShellStdout = (props?: { +export const useSandpackShellStdout = ({ + clientId, + maxMessageCount = MAX_MESSAGE_COUNT, + resetOnPreviewRestart = false, +}: { clientId?: string; maxMessageCount?: number; + resetOnPreviewRestart: boolean; }): { logs: Array<{ id: string; data: string }>; reset: () => void; @@ -18,12 +23,11 @@ export const useSandpackShellStdout = (props?: { ); const { listen } = useSandpack(); - const maxMessageCount = props?.maxMessageCount ?? MAX_MESSAGE_COUNT; - const clientId = props?.clientId; - React.useEffect(() => { const unsubscribe = listen((message) => { - if ( + if (message.type === "start") { + setLogs([]); + } else if ( message.type === "stdout" && message.payload.data && Boolean(message.payload.data.trim()) diff --git a/website/docs/src/pages/advanced-usage/components.mdx b/website/docs/src/pages/advanced-usage/components.mdx index c6fe35869..bfc1097b2 100644 --- a/website/docs/src/pages/advanced-usage/components.mdx +++ b/website/docs/src/pages/advanced-usage/components.mdx @@ -536,14 +536,15 @@ export default () => (
`SandpackConsole` Options -| Prop | Type | Default | -| :- | :- | :- | -| `clientId` | `string` | `undefined` | -| `showHeader` | `boolean` | `true` | -| `showSyntaxError` | `boolean` | `false` | -| `maxMessageCount` | `number` | `800` | -| `onLogsChange` | `(logs: SandpackConsoleData) => void` | | - +| Prop | Type | Default | Description | +| :- | :- | :- | :- | +| `clientId` | `string` | `undefined` | | +| `showHeader` | `boolean` | `true` | | +| `showSyntaxError` | `boolean` | `false` | | +| `maxMessageCount` | `number` | `800` | | +| `onLogsChange` | `(logs: SandpackConsoleData) => void` | | | +| `resetOnPreviewRestart` | `boolean` | `false` | Reset the console list on every preview restart | +| `ref` | `SandpackConsoleRef` | `SandpackConsoleRef` | Make possible to imperatively interact with the console component |