Skip to content

Commit 268c6f6

Browse files
authored
Support output tabs in pyodide (#1157)
1 parent bbdfad8 commit 268c6f6

File tree

4 files changed

+101
-32
lines changed

4 files changed

+101
-32
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## Unreleased
88

9+
### Added
10+
11+
- Support for the `outputPanels` attribute in the `PyodideRunner` (#1157)
12+
913
## [0.28.14] - 2025-01-06
1014

1115
### Fixed

src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx

+45-30
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const getWorkerURL = (url) => {
3131
return URL.createObjectURL(blob);
3232
};
3333

34-
const PyodideRunner = ({ active }) => {
34+
const PyodideRunner = ({ active, outputPanels = ["text", "visual"] }) => {
3535
const [pyodideWorker, setPyodideWorker] = useState(null);
3636

3737
useEffect(() => {
@@ -65,6 +65,9 @@ const PyodideRunner = ({ active }) => {
6565
const isMobile = useMediaQuery({ query: MOBILE_MEDIA_QUERY });
6666
const senseHatAlways = useSelector((s) => s.editor.senseHatAlwaysEnabled);
6767
const queryParams = new URLSearchParams(window.location.search);
68+
const singleOutputPanel = outputPanels.length === 1;
69+
const showVisualOutputPanel = outputPanels.includes("visual");
70+
const showTextOutputPanel = outputPanels.includes("text");
6871
const showVisualTab = queryParams.get("show_visual_tab") === "true";
6972
const [hasVisual, setHasVisual] = useState(showVisualTab || senseHatAlways);
7073
const [visuals, setVisuals] = useState([]);
@@ -195,8 +198,10 @@ const PyodideRunner = ({ active }) => {
195198
};
196199

197200
const handleVisual = (origin, content) => {
198-
setHasVisual(true);
199-
setVisuals((array) => [...array, { origin, content }]);
201+
if (showVisualOutputPanel) {
202+
setHasVisual(true);
203+
setVisuals((array) => [...array, { origin, content }]);
204+
}
200205
};
201206

202207
const handleSenseHatEvent = (type) => {
@@ -326,12 +331,16 @@ const PyodideRunner = ({ active }) => {
326331
"pyodiderunner--active": active,
327332
})}
328333
>
329-
{isSplitView ? (
334+
{isSplitView || singleOutputPanel ? (
330335
<>
331-
{hasVisual && (
336+
{hasVisual && showVisualOutputPanel && (
332337
<div className="output-panel output-panel--visual">
333338
<Tabs forceRenderTabPanel={true}>
334-
<div className="react-tabs__tab-container">
339+
<div
340+
className={classNames("react-tabs__tab-container", {
341+
"react-tabs__tab-container--hidden": singleOutputPanel,
342+
})}
343+
>
335344
<TabList>
336345
<Tab key={0}>
337346
<span className="react-tabs__tab-text">
@@ -348,30 +357,36 @@ const PyodideRunner = ({ active }) => {
348357
</Tabs>
349358
</div>
350359
)}
351-
<div className="output-panel output-panel--text">
352-
<Tabs forceRenderTabPanel={true}>
353-
<div className="react-tabs__tab-container">
354-
<TabList>
355-
<Tab key={0}>
356-
<span className="react-tabs__tab-text">
357-
{t("output.textOutput")}
358-
</span>
359-
</Tab>
360-
</TabList>
361-
{!hasVisual && !isEmbedded && isMobile && (
362-
<RunnerControls skinny />
363-
)}
364-
</div>
365-
<ErrorMessage />
366-
<TabPanel key={0}>
367-
<pre
368-
className={`pythonrunner-console pythonrunner-console--${settings.fontSize}`}
369-
onClick={shiftFocusToInput}
370-
ref={output}
371-
></pre>
372-
</TabPanel>
373-
</Tabs>
374-
</div>
360+
{showTextOutputPanel && (
361+
<div className="output-panel output-panel--text">
362+
<Tabs forceRenderTabPanel={true}>
363+
<div
364+
className={classNames("react-tabs__tab-container", {
365+
"react-tabs__tab-container--hidden": singleOutputPanel,
366+
})}
367+
>
368+
<TabList>
369+
<Tab key={0}>
370+
<span className="react-tabs__tab-text">
371+
{t("output.textOutput")}
372+
</span>
373+
</Tab>
374+
</TabList>
375+
{!hasVisual && !isEmbedded && isMobile && (
376+
<RunnerControls skinny />
377+
)}
378+
</div>
379+
<ErrorMessage />
380+
<TabPanel key={0}>
381+
<pre
382+
className={`pythonrunner-console pythonrunner-console--${settings.fontSize}`}
383+
onClick={shiftFocusToInput}
384+
ref={output}
385+
></pre>
386+
</TabPanel>
387+
</Tabs>
388+
</div>
389+
)}
375390
</>
376391
) : (
377392
<Tabs forceRenderTabPanel={true} defaultIndex={hasVisual ? 0 : 1}>

src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.test.js

+48-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
setProject,
1818
setLoadedRunner,
1919
stopCodeRun,
20+
setSenseHatAlwaysEnabled,
2021
} from "../../../../../redux/EditorSlice.js";
2122
import store from "../../../../../app/store";
2223

@@ -26,7 +27,11 @@ global.fetch = jest.fn();
2627
const project = {
2728
components: [
2829
{ name: "a", extension: "py", content: "print('a')" },
29-
{ name: "main", extension: "py", content: "print('hello')" },
30+
{
31+
name: "main",
32+
extension: "py",
33+
content: "print('hello')",
34+
},
3035
],
3136
image_list: [
3237
{ filename: "image1.jpg", url: "http://example.com/image1.jpg" },
@@ -368,3 +373,45 @@ describe("When not active and code run triggered", () => {
368373
expect(postMessage).not.toHaveBeenCalled();
369374
});
370375
});
376+
377+
describe("When there is visual output", () => {
378+
beforeEach(() => {
379+
act(() => {
380+
store.dispatch(setSenseHatAlwaysEnabled(true));
381+
});
382+
});
383+
384+
test("displays both text and visual tabs by default", () => {
385+
render(
386+
<Provider store={store}>
387+
<PyodideRunner />
388+
</Provider>,
389+
);
390+
expect(screen.queryByText("output.textOutput")).toBeInTheDocument();
391+
expect(screen.queryByText("output.visualOutput")).toBeInTheDocument();
392+
});
393+
394+
test("only displays text tab when outputPanels is set to just text", () => {
395+
render(
396+
<Provider store={store}>
397+
<PyodideRunner outputPanels={["text"]} />
398+
</Provider>,
399+
);
400+
expect(screen.queryByText("output.textOutput")).toBeInTheDocument();
401+
expect(screen.queryByText("output.visualOutput")).not.toBeInTheDocument();
402+
});
403+
404+
test("only displays visual tab when outputPanels is set to just visual", () => {
405+
render(
406+
<Provider store={store}>
407+
<PyodideRunner outputPanels={["visual"]} />
408+
</Provider>,
409+
);
410+
expect(screen.queryByText("output.textOutput")).not.toBeInTheDocument();
411+
expect(screen.queryByText("output.visualOutput")).toBeInTheDocument();
412+
});
413+
414+
afterEach(() => {
415+
store.dispatch(setSenseHatAlwaysEnabled(false));
416+
});
417+
});

src/components/Editor/Runners/PythonRunner/PythonRunner.jsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ const PythonRunner = ({ outputPanels = ["text", "visual"] }) => {
9292
}, [project, codeRunTriggered, senseHatAlwaysEnabled, skulptFallback, t]);
9393
return (
9494
<>
95-
<PyodideRunner active={activeRunner === "pyodide"} />
95+
<PyodideRunner
96+
active={activeRunner === "pyodide"}
97+
outputPanels={outputPanels}
98+
/>
9699
<SkulptRunner
97100
active={activeRunner === "skulpt"}
98101
outputPanels={outputPanels}

0 commit comments

Comments
 (0)