Skip to content

Block-to-text Output only Web Component #782

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## Unreleased

### Added

- `assets_identifier` attribute for the web component (#901)
- `code` attribute overriding content of `main.py`/`index.html` in the web component (#901)
- Web component methods to run, stop and rerun the code from the host page (#899)
- Enable web component to specify which output tabs are shown (#909)
- Return more verbose errors to web component (#915)

### Changed

- Made `p5` canvas responsive to the available space (#887)
Expand Down
2 changes: 1 addition & 1 deletion src/assets/stylesheets/EmbeddedViewer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
}
}

.--light {
#app.--light {
.embedded-viewer {
background-color: $rpf-white;
}
Expand Down
1 change: 1 addition & 0 deletions src/assets/stylesheets/InternalStyles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
@use "./ErrorMessage" as *;
@use "./OutputViewToggle" as *;
@use "./AstroPiModel" as *;
@use "./EmbeddedViewer" as *;
@use "./MobileProjectBar" as *;
@use "./ProjectBar" as *;
@use "./Button" as *;
Expand Down
4 changes: 4 additions & 0 deletions src/assets/stylesheets/PythonRunner.scss
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,7 @@
}
}
}

.--light .output-panel--single, .--dark .output-panel--single {
border-block-end: none;
}
4 changes: 4 additions & 0 deletions src/assets/stylesheets/Tabs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@
padding: 0 $space-0-25 0 0;
}

&__tab-container--hidden {
display: none;
}

&__tab-panel--selected {
flex: 1;
display: flex;
Expand Down
2 changes: 1 addition & 1 deletion src/components/Editor/EditorPanel/EditorPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const EditorPanel = ({ extension = "html", fileName = "index" }) => {
useEffect(() => {
const code = project.components.find(
(item) => item.extension === extension && item.name === fileName,
).content;
)?.content;
const mode = getMode();
const startState = EditorState.create({
doc: code,
Expand Down
22 changes: 17 additions & 5 deletions src/components/Editor/Output/Output.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,30 @@ import ExternalFiles from "../../ExternalFiles/ExternalFiles";
import RunnerFactory from "../Runners/RunnerFactory";
import RunBar from "../../RunButton/RunBar";

const Output = () => {
const Output = ({
embedded = false,
browserPreview = false,
outputPanels = ["text", "visual"],
}) => {
const project = useSelector((state) => state.editor.project);
const isEmbedded = useSelector((state) => state.editor.isEmbedded);
const isEmbedded =
useSelector((state) => state.editor.isEmbedded) || embedded;
const searchParams = new URLSearchParams(window.location.search);
const isBrowserPreview = searchParams.get("browserPreview") === "true";
const isBrowserPreview =
searchParams.get("browserPreview") === "true" || browserPreview;
const webComponent = useSelector((state) => state.editor.webComponent);

return (
<>
<ExternalFiles />
<div className="proj-runner-container">
<RunnerFactory projectType={project.project_type} />
{isEmbedded && !isBrowserPreview ? <RunBar embedded /> : null}
<RunnerFactory
projectType={project.project_type}
outputPanels={outputPanels}
/>
{!webComponent && isEmbedded && !isBrowserPreview && (
<RunBar embedded />
)}
</div>
</>
);
Expand Down
128 changes: 85 additions & 43 deletions src/components/Editor/Runners/PythonRunner/PythonRunner.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Sk from "skulpt";
import { useMediaQuery } from "react-responsive";
import {
setError,
setErrorDetails,
codeRunHandled,
stopDraw,
setSenseHatEnabled,
Expand All @@ -21,6 +22,7 @@ import OutputViewToggle from "./OutputViewToggle";
import { SettingsContext } from "../../../../utils/settings";
import RunnerControls from "../../../RunButton/RunnerControls";
import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints";
import classNames from "classnames";

const externalLibraries = {
"./pygal/__init__.js": {
Expand Down Expand Up @@ -52,14 +54,15 @@ const externalLibraries = {
},
};

const PythonRunner = () => {
const PythonRunner = ({ outputPanels = ["text", "visual"] }) => {
const projectCode = useSelector((state) => state.editor.project.components);
const projectIdentifier = useSelector(
(state) => state.editor.project.identifier,
);
const user = useSelector((state) => state.auth.user);
const isSplitView = useSelector((state) => state.editor.isSplitView);
const isEmbedded = useSelector((state) => state.editor.isEmbedded);
const isOutputOnly = useSelector((state) => state.editor.isOutputOnly);
const codeRunTriggered = useSelector(
(state) => state.editor.codeRunTriggered,
);
Expand Down Expand Up @@ -125,11 +128,13 @@ const PythonRunner = () => {
const outf = (text) => {
if (text !== "") {
const node = output.current;
const div = document.createElement("span");
div.classList.add("pythonrunner-console-output-line");
div.innerHTML = new Option(text).innerHTML;
node.appendChild(div);
node.scrollTop = node.scrollHeight;
if (node) {
const div = document.createElement("span");
div.classList.add("pythonrunner-console-output-line");
div.innerHTML = new Option(text).innerHTML;
node.appendChild(div);
node.scrollTop = node.scrollHeight;
}
}
};

Expand Down Expand Up @@ -274,11 +279,16 @@ const PythonRunner = () => {
};

const handleError = (err) => {
let errorDetails = {};
let errorMessage;
if (err.message === t("output.errors.interrupted")) {
errorMessage = err.message;
errorDetails = {
type: "Interrupted",
message: errorMessage,
};
} else {
const errorDetails = (err.tp$str && err.tp$str().v)
const errorDescription = (err.tp$str && err.tp$str().v)
.replace(/\[(.*?)\]/, "")
.replace(/\.$/, "");
const errorType = err.tp$name || err.constructor.name;
Expand All @@ -290,14 +300,23 @@ const PythonRunner = () => {
userId = user.profile?.user;
}

errorMessage = `${errorType}: ${errorDetails} on line ${lineNumber} of ${fileName}`;
errorMessage = `${errorType}: ${errorDescription} on line ${lineNumber} of ${fileName}`;
createError(projectIdentifier, userId, {
errorType,
errorMessage,
});

errorDetails = {
type: errorType,
line: lineNumber,
file: fileName,
description: errorDescription,
message: errorMessage,
};
}

dispatch(setError(errorMessage));
dispatch(setErrorDetails(errorDetails));
dispatch(stopDraw());
if (getInput()) {
const input = getInput();
Expand All @@ -309,7 +328,10 @@ const PythonRunner = () => {
const runCode = () => {
// clear previous output
dispatch(setError(""));
output.current.innerHTML = "";
dispatch(setErrorDetails({}));
if (output.current) {
output.current.innerHTML = "";
}
dispatch(setSenseHatEnabled(false));

var prog = projectCode[0].content;
Expand Down Expand Up @@ -372,54 +394,74 @@ const PythonRunner = () => {
}
}

const singleOutputPanel = outputPanels.length === 1;
const showVisualOutput = outputPanels.includes("visual");
const showTextOutput = outputPanels.includes("text");

const outputPanelClasses = (panelType) => {
return classNames("output-panel", `output-panel--${panelType}`, {
"output-panel--single": singleOutputPanel,
});
};

return (
<div className={`pythonrunner-container`}>
{isSplitView ? (
{isSplitView || singleOutputPanel ? (
<>
{hasVisualOutput ? (
<div className="output-panel output-panel--visual">
{hasVisualOutput && showVisualOutput && (
<div className={outputPanelClasses("visual")}>
<Tabs forceRenderTabPanel={true}>
<div className="react-tabs__tab-container">
<div
className={classNames("react-tabs__tab-container", {
"react-tabs__tab-container--hidden": singleOutputPanel,
})}
>
<TabList>
<Tab key={0}>
<span className="react-tabs__tab-text">
{t("output.visualOutput")}
</span>
</Tab>
</TabList>
{!isEmbedded && hasVisualOutput ? <OutputViewToggle /> : null}
{!isEmbedded && isMobile ? <RunnerControls skinny /> : null}
{!isEmbedded && hasVisualOutput && <OutputViewToggle />}
{!isEmbedded && isMobile && <RunnerControls skinny />}
</div>
<TabPanel key={0}>
<VisualOutputPane />
</TabPanel>
</Tabs>
</div>
) : null}
<div className="output-panel output-panel--text">
<Tabs forceRenderTabPanel={true}>
<div className="react-tabs__tab-container">
<TabList>
<Tab key={0}>
<span className="react-tabs__tab-text">
{t("output.textOutput")}
</span>
</Tab>
</TabList>
{!hasVisualOutput && !isEmbedded && isMobile ? (
<RunnerControls skinny />
) : null}
</div>
<ErrorMessage />
<TabPanel key={0}>
<pre
className={`pythonrunner-console pythonrunner-console--${settings.fontSize}`}
onClick={shiftFocusToInput}
ref={output}
></pre>
</TabPanel>
</Tabs>
</div>
)}
{showTextOutput && (
<div className={outputPanelClasses("text")}>
<Tabs forceRenderTabPanel={true}>
<div
className={classNames("react-tabs__tab-container", {
"react-tabs__tab-container--hidden": singleOutputPanel,
})}
>
<TabList>
<Tab key={0}>
<span className="react-tabs__tab-text">
{t("output.textOutput")}
</span>
</Tab>
</TabList>
{!hasVisualOutput && !isEmbedded && isMobile && (
<RunnerControls skinny />
)}
</div>
<ErrorMessage />
<TabPanel key={0}>
<pre
className={`pythonrunner-console pythonrunner-console--${settings.fontSize}`}
onClick={shiftFocusToInput}
ref={output}
></pre>
</TabPanel>
</Tabs>
</div>
)}
</>
) : (
<Tabs forceRenderTabPanel={true} defaultIndex={hasVisualOutput ? 0 : 1}>
Expand All @@ -438,10 +480,10 @@ const PythonRunner = () => {
</span>
</Tab>
</TabList>
{!isEmbedded && hasVisualOutput ? <OutputViewToggle /> : null}
{!isEmbedded && isMobile ? <RunnerControls skinny /> : null}
{!isEmbedded && hasVisualOutput && <OutputViewToggle />}
{!isEmbedded && isMobile && <RunnerControls skinny />}
</div>
<ErrorMessage />
{!isOutputOnly && <ErrorMessage />}
{hasVisualOutput ? (
<TabPanel key={0}>
<VisualOutputPane />
Expand Down
6 changes: 4 additions & 2 deletions src/components/Editor/Runners/RunnerFactory.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import PythonRunner from "./PythonRunner/PythonRunner";
import HtmlRunner from "./HtmlRunner/HtmlRunner";

const RunnerFactory = ({ projectType }) => {
const RunnerFactory = ({ projectType, outputPanels = ["text", "visual"] }) => {
const Runner = () => {
if (projectType === "html") {
return HtmlRunner;
Expand All @@ -12,7 +12,9 @@ const RunnerFactory = ({ projectType }) => {

const Selected = Runner();

return <Selected />;
const props = projectType === "html" ? {} : { outputPanels };

return <Selected {...props} />;
};

export default RunnerFactory;
2 changes: 1 addition & 1 deletion src/components/ExternalFiles/ExternalFiles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const ExternalFiles = () => {

return (
<div id="file-content" hidden>
{project.components.map((file, i) => {
{project.components?.map((file, i) => {
if (["csv", "txt"].includes(file.extension)) {
return (
<div id={`${file.name}.${file.extension}`} key={i}>
Expand Down
Loading