Skip to content

Commit

Permalink
fix(console): improvements (#743)
Browse files Browse the repository at this point in the history
  • Loading branch information
danilowoz committed Feb 20, 2023
1 parent 3e5f5ce commit 685fac3
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 128 deletions.
19 changes: 18 additions & 1 deletion sandpack-react/src/components/Console/Console.stories.tsx
Expand Up @@ -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",
Expand Down Expand Up @@ -130,3 +130,20 @@ export const Preset: React.FC = () => {
</div>
);
};

export const ImperativeReset: React.FC = () => {
const consoleRef = React.useRef<SandpackConsoleRef>(null);

const resetLogs = () => {
consoleRef.current?.reset();
};

return (
<SandpackProvider>
<SandpackCodeEditor />
<SandpackPreview />
<button onClick={resetLogs}>Reset logs</button>
<SandpackConsole ref={consoleRef} />
</SandpackProvider>
);
};
237 changes: 129 additions & 108 deletions 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";
Expand All @@ -22,6 +22,11 @@ interface SandpackConsoleProps {
showSetupProgress?: boolean;
maxMessageCount?: number;
onLogsChange?: (logs: SandpackConsoleData) => void;
resetOnPreviewRestart?: boolean;
}

export interface SandpackConsoleRef {
reset: () => void;
}

/**
Expand All @@ -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<HTMLDivElement> & 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<HTMLDivElement>(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 (
<SandpackStack
className={classNames(
css({
height: "100%",
background: "$surface1",
iframe: { display: "none" },
}),
`${THEME_PREFIX}-console`,
className
)}
{...props}
>
{showSetupProgress && (
<PreviewProgress timeout={1_000} dismissOnTimeout />
)}

{(showHeader || isNodeEnvironment) && (
<Header
currentTab={currentTab}
node={isNodeEnvironment}
setCurrentTab={setCurrentTab}
/>
)}

<div
ref={wrapperRef}
>(
(
{
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<HTMLDivElement>(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 (
<SandpackStack
className={classNames(
css({ overflow: "auto", scrollBehavior: "smooth" })
css({
height: "100%",
background: "$surface1",
iframe: { display: "none" },
}),
`${THEME_PREFIX}-console`,
className
)}
{...props}
>
{isServerTab ? (
<StdoutList data={stdoutData} />
) : (
<ConsoleList data={consoleData} />
{showSetupProgress && (
<PreviewProgress timeout={1_000} dismissOnTimeout />
)}
</div>

<div
className={classNames(
css({
position: "absolute",
bottom: "$space$2",
right: "$space$2",
display: "flex",
gap: "$space$2",
})
{(showHeader || isNodeEnvironment) && (
<Header
currentTab={currentTab}
node={isNodeEnvironment}
setCurrentTab={setCurrentTab}
/>
)}
>
{isServerTab && (

<div
ref={wrapperRef}
className={classNames(
css({ overflow: "auto", scrollBehavior: "smooth" })
)}
>
{isServerTab ? (
<StdoutList data={stdoutData} />
) : (
<ConsoleList data={consoleData} />
)}
</div>

<div
className={classNames(
css({
position: "absolute",
bottom: "$space$2",
right: "$space$2",
display: "flex",
gap: "$space$2",
})
)}
>
{isServerTab && (
<RoundedButton
onClick={(): void => {
restart();
resetConsole();
resetStdout();
}}
>
<RestartIcon />
</RoundedButton>
)}

<RoundedButton
onClick={(): void => {
restart();
resetConsole();
resetStdout();
if (currentTab === "client") {
resetConsole();
} else {
resetStdout();
}
}}
>
<RestartIcon />
<CleanIcon />
</RoundedButton>
)}

<RoundedButton
onClick={(): void => {
if (currentTab === "client") {
resetConsole();
} else {
resetStdout();
}
}}
>
<CleanIcon />
</RoundedButton>
</div>
</SandpackStack>
);
};
</div>
</SandpackStack>
);
}
);
16 changes: 10 additions & 6 deletions sandpack-react/src/components/Console/useSandpackConsole.ts
Expand Up @@ -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<SandpackConsoleData>([]);
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];
Expand Down
14 changes: 9 additions & 5 deletions sandpack-react/src/hooks/useSandpackShellStdout.ts
Expand Up @@ -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;
Expand All @@ -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())
Expand Down
17 changes: 9 additions & 8 deletions website/docs/src/pages/advanced-usage/components.mdx
Expand Up @@ -536,14 +536,15 @@ export default () => (
<details>
<summary>`SandpackConsole` Options</summary>
| 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 |
</details>
Expand Down

1 comment on commit 685fac3

@vercel
Copy link

@vercel vercel bot commented on 685fac3 Feb 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

sandpack-docs – ./website/docs

sandpack-docs-git-main-codesandbox1.vercel.app
sandpack-docs-codesandbox1.vercel.app
sandpack.vercel.app

Please sign in to comment.