From 6595dced124e2698e5b278f4787903535091a90e Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Wed, 16 Oct 2024 15:01:28 +0100 Subject: [PATCH 01/13] initial console functionality --- src/assets/stylesheets/PythonRunner.scss | 1 - src/components/Editor/Output/Output.jsx | 3 +- src/components/Editor/Project/Project.jsx | 3 +- .../PyodideRunner/PyodideRunner.jsx | 68 ++++++++++++++++++- .../Runners/PythonRunner/PythonRunner.jsx | 15 +++- .../SkulptRunner/SkulptRunner.jsx | 2 +- .../Editor/Runners/RunnerFactory.jsx | 8 ++- .../Mobile/MobileProject/MobileProject.jsx | 8 ++- .../WebComponentProject.jsx | 3 + src/containers/WebComponentLoader.jsx | 2 + src/web-component.js | 2 + 11 files changed, 103 insertions(+), 12 deletions(-) diff --git a/src/assets/stylesheets/PythonRunner.scss b/src/assets/stylesheets/PythonRunner.scss index abfa7091b..4e2fba8f1 100644 --- a/src/assets/stylesheets/PythonRunner.scss +++ b/src/assets/stylesheets/PythonRunner.scss @@ -41,7 +41,6 @@ .pythonrunner-input { caret-color: inherit; color: rgb(36, 103, 236); - display: block; line-height: 20px; padding: 2px 1px 2px 0; } diff --git a/src/components/Editor/Output/Output.jsx b/src/components/Editor/Output/Output.jsx index ba55119bc..10e903783 100644 --- a/src/components/Editor/Output/Output.jsx +++ b/src/components/Editor/Output/Output.jsx @@ -4,7 +4,7 @@ import ExternalFiles from "../../ExternalFiles/ExternalFiles"; import RunnerFactory from "../Runners/RunnerFactory"; import RunBar from "../../RunButton/RunBar"; -const Output = ({ outputPanels = ["text", "visual"] }) => { +const Output = ({ outputPanels = ["text", "visual"] }, autoRun = false) => { const project = useSelector((state) => state.editor.project); const isEmbedded = useSelector((state) => state.editor.isEmbedded); const searchParams = new URLSearchParams(window.location.search); @@ -17,6 +17,7 @@ const Output = ({ outputPanels = ["text", "visual"] }) => { {isEmbedded && !isBrowserPreview && } diff --git a/src/components/Editor/Project/Project.jsx b/src/components/Editor/Project/Project.jsx index 4fa543b97..7e7fa6992 100644 --- a/src/components/Editor/Project/Project.jsx +++ b/src/components/Editor/Project/Project.jsx @@ -18,6 +18,7 @@ import { projContainer } from "../../../utils/containerQueries"; const Project = (props) => { const webComponent = useSelector((state) => state.editor.webComponent); const { + autoRun = false, nameEditable = true, withProjectbar = true, withSidebar = true, @@ -75,7 +76,7 @@ const Project = (props) => { > - + )} diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index 8bb232feb..64716cd62 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -7,6 +7,7 @@ import { setError, codeRunHandled, loadingRunner, + triggerCodeRun, } from "../../../../../redux/EditorSlice"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import { useMediaQuery } from "react-responsive"; @@ -31,7 +32,7 @@ const getWorkerURL = (url) => { }; const PyodideRunner = (props) => { - const { active } = props; + const { active, consoleMode = false, autoRun = false } = props; // Blob approach + targeted headers - no errors but headers required in host app to interrupt code const workerUrl = getWorkerURL(`${process.env.PUBLIC_URL}/PyodideWorker.js`); @@ -60,6 +61,53 @@ const PyodideRunner = (props) => { const [hasVisual, setHasVisual] = useState(showVisualTab || senseHatAlways); const [visuals, setVisuals] = useState([]); const [showRunner, setShowRunner] = useState(active); + const [inputStack, setInputStack] = useState([]); + const prependToInputStack = (input) => { + setInputStack((prevInputStack) => { + if (prevInputStack[0] === "") { + console.log("overwriting..."); + const newStack = [...prevInputStack]; + newStack[0] = input; + return newStack; + } else { + console.log("prepending..."); + console.log(prevInputStack); + return [input, ...prevInputStack]; + } + }); + }; + const [inputStackIndex, setInputStackIndex] = useState(0); + + useEffect(() => { + const handleKeyDown = (event) => { + if (event.key === "ArrowUp") { + if (inputStackIndex < inputStack.length - 1) { + setInputStackIndex(inputStackIndex + 1); + } + } else if (event.key === "ArrowDown") { + if (inputStackIndex > 0) { + setInputStackIndex(inputStackIndex - 1); + } + } + }; + if (consoleMode) { + const inputElement = getInputElement(); + inputElement?.removeEventListener("keydown", handleKeyDown); + inputElement?.addEventListener("keydown", handleKeyDown); + } + }, [inputStack, inputStackIndex, consoleMode]); + + useEffect(() => { + console.log("inputStack", inputStack); + }, [inputStack]); + + useEffect(() => { + console.log("inputStackIndex", inputStackIndex); + const inputElement = getInputElement(); + if (inputElement) { + inputElement.innerText = inputStack[inputStackIndex]; + } + }, [inputStackIndex]); useEffect(() => { if (pyodideWorker) { @@ -99,6 +147,13 @@ const PyodideRunner = (props) => { } }, []); + useEffect(() => { + if (autoRun) { + console.log("autorunning..."); + dispatch(triggerCodeRun()); + } + }, []); + useEffect(() => { if (codeRunTriggered && active) { console.log("running with pyodide"); @@ -137,11 +192,19 @@ const PyodideRunner = (props) => { return; } + prependToInputStack(""); + setInputStackIndex(0); const outputPane = output.current; - outputPane.appendChild(inputSpan()); + // remove last new line character from last line + outputPane.lastChild.innerText = outputPane.lastChild.innerText.slice( + 0, + -1, + ); + outputPane.lastChild.appendChild(inputSpan()); const element = getInputElement(); const { content, ctrlD } = await getInputContent(element); + prependToInputStack(content); const encoder = new TextEncoder(); const bytes = encoder.encode(content + "\n"); @@ -305,6 +368,7 @@ const PyodideRunner = (props) => { if (element) { element.removeAttribute("id"); element.removeAttribute("contentEditable"); + element.addEventListener("keydown"); } }; diff --git a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx index 01bcf7ca5..0458b0796 100644 --- a/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PythonRunner.jsx @@ -13,7 +13,7 @@ const SKULPT_ONLY_MODULES = [ "turtle", ]; -const PythonRunner = () => { +const PythonRunner = ({ autoRun = false }) => { const project = useSelector((state) => state.editor.project); const codeRunTriggered = useSelector( (state) => state.editor.codeRunTriggered, @@ -22,6 +22,7 @@ const PythonRunner = () => { (state) => state.editor.senseHatAlwaysEnabled, ); const [usePyodide, setUsePyodide] = useState(true); + const [consoleMode, setConsoleMode] = useState(false); const { t } = useTranslation(); useEffect(() => { @@ -64,6 +65,12 @@ const PythonRunner = () => { break; } else { setUsePyodide(true); + if (imports.includes("IPython")) { + setConsoleMode(true); + break; + } else { + setConsoleMode(false); + } } } catch (error) { console.error("Error occurred while getting imports:", error); @@ -73,7 +80,11 @@ const PythonRunner = () => { }, [project, codeRunTriggered, senseHatAlwaysEnabled, t]); return ( <> - + ); diff --git a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx index dc852989c..9603dbf0d 100644 --- a/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx @@ -259,7 +259,7 @@ const SkulptRunner = ({ active, outputPanels = ["text", "visual"] }) => { Sk.sense_hat.mz_criteria.noInputEvents = false; } const outputPane = output.current; - outputPane.appendChild(inputSpan()); + outputPane.lastChild.appendChild(inputSpan()); const input = getInput(); input.focus(); diff --git a/src/components/Editor/Runners/RunnerFactory.jsx b/src/components/Editor/Runners/RunnerFactory.jsx index 37f7f8b6f..c7da97447 100644 --- a/src/components/Editor/Runners/RunnerFactory.jsx +++ b/src/components/Editor/Runners/RunnerFactory.jsx @@ -2,7 +2,11 @@ import React from "react"; import PythonRunner from "./PythonRunner/PythonRunner"; import HtmlRunner from "./HtmlRunner/HtmlRunner"; -const RunnerFactory = ({ projectType, outputPanels = ["text", "visual"] }) => { +const RunnerFactory = ({ + autoRun, + projectType, + outputPanels = ["text", "visual"], +}) => { const Runner = () => { if (projectType === "html") { return HtmlRunner; @@ -13,7 +17,7 @@ const RunnerFactory = ({ projectType, outputPanels = ["text", "visual"] }) => { const Selected = Runner(); - const props = projectType === "html" ? {} : { outputPanels }; + const props = projectType === "html" ? {} : { outputPanels, autoRun }; return ; }; diff --git a/src/components/Mobile/MobileProject/MobileProject.jsx b/src/components/Mobile/MobileProject/MobileProject.jsx index 50cc22a65..f4cab0aa3 100644 --- a/src/components/Mobile/MobileProject/MobileProject.jsx +++ b/src/components/Mobile/MobileProject/MobileProject.jsx @@ -14,7 +14,11 @@ import { useTranslation } from "react-i18next"; import Sidebar from "../../Menus/Sidebar/Sidebar"; import { showSidebar } from "../../../redux/EditorSlice"; -const MobileProject = ({ withSidebar, sidebarOptions = [] }) => { +const MobileProject = ({ + autoRun = false, + withSidebar, + sidebarOptions = [], +}) => { const projectType = useSelector((state) => state.editor.project.project_type); const sidebarShowing = useSelector((state) => state.editor.sidebarShowing); const codeRunTriggered = useSelector( @@ -55,7 +59,7 @@ const MobileProject = ({ withSidebar, sidebarOptions = [] }) => { - +
diff --git a/src/components/WebComponentProject/WebComponentProject.jsx b/src/components/WebComponentProject/WebComponentProject.jsx index 08fa2eb55..80fcb7e44 100644 --- a/src/components/WebComponentProject/WebComponentProject.jsx +++ b/src/components/WebComponentProject/WebComponentProject.jsx @@ -24,6 +24,7 @@ import { } from "../../events/WebComponentCustomEvents"; const WebComponentProject = ({ + autoRun = false, withProjectbar = false, nameEditable = false, withSidebar = false, @@ -97,11 +98,13 @@ const WebComponentProject = ({ {!outputOnly && (isMobile ? ( ) : ( { const { assetsIdentifier, authKey, + autoRun = false, identifier, code, senseHatAlwaysEnabled = false, @@ -175,6 +176,7 @@ const WebComponentLoader = (props) => { closeButton={ToastCloseButton} /> Date: Wed, 16 Oct 2024 15:17:10 +0100 Subject: [PATCH 02/13] fixing autorun --- src/components/Editor/Output/Output.jsx | 2 +- .../Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx | 1 - src/components/Editor/Runners/RunnerFactory.jsx | 2 +- src/components/WebComponentProject/WebComponentProject.jsx | 4 +++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/Editor/Output/Output.jsx b/src/components/Editor/Output/Output.jsx index 10e903783..fd1b408d0 100644 --- a/src/components/Editor/Output/Output.jsx +++ b/src/components/Editor/Output/Output.jsx @@ -4,7 +4,7 @@ import ExternalFiles from "../../ExternalFiles/ExternalFiles"; import RunnerFactory from "../Runners/RunnerFactory"; import RunBar from "../../RunButton/RunBar"; -const Output = ({ outputPanels = ["text", "visual"] }, autoRun = false) => { +const Output = ({ outputPanels = ["text", "visual"], autoRun = false }) => { const project = useSelector((state) => state.editor.project); const isEmbedded = useSelector((state) => state.editor.isEmbedded); const searchParams = new URLSearchParams(window.location.search); diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index 64716cd62..440e21237 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -149,7 +149,6 @@ const PyodideRunner = (props) => { useEffect(() => { if (autoRun) { - console.log("autorunning..."); dispatch(triggerCodeRun()); } }, []); diff --git a/src/components/Editor/Runners/RunnerFactory.jsx b/src/components/Editor/Runners/RunnerFactory.jsx index c7da97447..5cf66333e 100644 --- a/src/components/Editor/Runners/RunnerFactory.jsx +++ b/src/components/Editor/Runners/RunnerFactory.jsx @@ -3,7 +3,7 @@ import PythonRunner from "./PythonRunner/PythonRunner"; import HtmlRunner from "./HtmlRunner/HtmlRunner"; const RunnerFactory = ({ - autoRun, + autoRun = false, projectType, outputPanels = ["text", "visual"], }) => { diff --git a/src/components/WebComponentProject/WebComponentProject.jsx b/src/components/WebComponentProject/WebComponentProject.jsx index 80fcb7e44..57590e627 100644 --- a/src/components/WebComponentProject/WebComponentProject.jsx +++ b/src/components/WebComponentProject/WebComponentProject.jsx @@ -113,7 +113,9 @@ const WebComponentProject = ({ ))} {outputOnly && (
- {loading === "success" && } + {loading === "success" && ( + + )}
)} From 3cab914ac292837a7e700fc943f6f8001c3ad367 Mon Sep 17 00:00:00 2001 From: Lois Wells Date: Thu, 17 Oct 2024 09:24:50 +0100 Subject: [PATCH 03/13] adding iframe console into actual editor and getting it autorunning with query string --- src/assets/stylesheets/PythonRunner.scss | 1 - src/components/Editor/Output/Output.jsx | 3 +- .../PyodideRunner/PyodideRunner.jsx | 39 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/assets/stylesheets/PythonRunner.scss b/src/assets/stylesheets/PythonRunner.scss index 4e2fba8f1..4c34bb7a4 100644 --- a/src/assets/stylesheets/PythonRunner.scss +++ b/src/assets/stylesheets/PythonRunner.scss @@ -17,7 +17,6 @@ white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; - width: 100%; word-wrap: break-word; overflow-y: auto; diff --git a/src/components/Editor/Output/Output.jsx b/src/components/Editor/Output/Output.jsx index fd1b408d0..92fe75d64 100644 --- a/src/components/Editor/Output/Output.jsx +++ b/src/components/Editor/Output/Output.jsx @@ -9,6 +9,7 @@ const Output = ({ outputPanels = ["text", "visual"], autoRun = false }) => { const isEmbedded = useSelector((state) => state.editor.isEmbedded); const searchParams = new URLSearchParams(window.location.search); const isBrowserPreview = searchParams.get("browserPreview") === "true"; + const isAutoRun = autoRun || searchParams.get("autoRun") === "true"; return ( <> @@ -17,7 +18,7 @@ const Output = ({ outputPanels = ["text", "visual"], autoRun = false }) => { {isEmbedded && !isBrowserPreview && }
diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index 440e21237..1e620bede 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -56,6 +56,7 @@ const PyodideRunner = (props) => { const settings = useContext(SettingsContext); const isMobile = useMediaQuery({ query: MOBILE_MEDIA_QUERY }); const senseHatAlways = useSelector((s) => s.editor.senseHatAlwaysEnabled); + const isOutputOnly = useSelector((state) => state.editor.isOutputOnly); const queryParams = new URLSearchParams(window.location.search); const showVisualTab = queryParams.get("show_visual_tab") === "true"; const [hasVisual, setHasVisual] = useState(showVisualTab || senseHatAlways); @@ -78,6 +79,10 @@ const PyodideRunner = (props) => { }; const [inputStackIndex, setInputStackIndex] = useState(0); + useEffect(() => { + console.log("isOutputOnly", isOutputOnly); + }); + useEffect(() => { const handleKeyDown = (event) => { if (event.key === "ArrowUp") { @@ -414,6 +419,11 @@ const PyodideRunner = (props) => { {t("output.textOutput")} + {!isOutputOnly && ( + + Console + + )} {!hasVisual && !isEmbedded && isMobile && ( @@ -427,6 +437,16 @@ const PyodideRunner = (props) => { ref={output} > + {!isOutputOnly && ( + +