From a262ea67a2915f2511681e8494c09fe46aab4b05 Mon Sep 17 00:00:00 2001
From: Renjie Li <renjie_li@outlook.com>
Date: Tue, 27 May 2025 15:23:46 +0800
Subject: [PATCH] add console context

---
 client/modules/IDE/components/Console.jsx     |   9 +-
 client/modules/IDE/context/ConsoleContext.jsx |  29 +++
 .../IDE/hooks/useHandleMessageEvent.js        |   5 +-
 client/modules/IDE/pages/IDEView.jsx          | 212 +++++++++---------
 4 files changed, 143 insertions(+), 112 deletions(-)
 create mode 100644 client/modules/IDE/context/ConsoleContext.jsx

diff --git a/client/modules/IDE/components/Console.jsx b/client/modules/IDE/components/Console.jsx
index 12d5c4988b..454b503998 100644
--- a/client/modules/IDE/components/Console.jsx
+++ b/client/modules/IDE/components/Console.jsx
@@ -1,24 +1,21 @@
 import React, { useRef, useEffect, useMemo } from 'react';
 import { useTranslation } from 'react-i18next';
-
 import { useSelector, useDispatch } from 'react-redux';
 import classNames from 'classnames';
 import { Console as ConsoleFeed } from 'console-feed';
 import ConsoleInput from './ConsoleInput';
-
 import UpArrowIcon from '../../../images/up-arrow.svg';
 import DownArrowIcon from '../../../images/down-arrow.svg';
-
 import * as IDEActions from '../../IDE/actions/ide';
-import * as ConsoleActions from '../../IDE/actions/console';
 import { useDidUpdate } from '../hooks/custom-hooks';
 import useHandleMessageEvent from '../hooks/useHandleMessageEvent';
 import { listen } from '../../../utils/dispatcher';
 import getConsoleFeedStyle from '../utils/consoleStyles';
+import { useConsole } from '../context/ConsoleContext';
 
 const Console = () => {
   const { t } = useTranslation();
-  const consoleEvents = useSelector((state) => state.console);
+  const { consoleEvents, clearConsole: clearConsoleContext } = useConsole();
   const isExpanded = useSelector((state) => state.ide.consoleIsExpanded);
   const isPlaying = useSelector((state) => state.ide.isPlaying);
   const { theme, fontSize } = useSelector((state) => state.preferences);
@@ -44,7 +41,7 @@ const Console = () => {
     };
   });
 
-  const handleClearConsole = () => dispatch(ConsoleActions.clearConsole());
+  const handleClearConsole = () => clearConsoleContext();
   const handleCollapseConsole = () => dispatch(IDEActions.collapseConsole());
   const handleExpandConsole = () => dispatch(IDEActions.expandConsole());
 
diff --git a/client/modules/IDE/context/ConsoleContext.jsx b/client/modules/IDE/context/ConsoleContext.jsx
new file mode 100644
index 0000000000..f1be4f31da
--- /dev/null
+++ b/client/modules/IDE/context/ConsoleContext.jsx
@@ -0,0 +1,29 @@
+import React, { createContext, useContext, useState, useCallback } from 'react';
+
+const ConsoleContext = createContext(null);
+
+export function ConsoleProvider({ children }) {
+  const [consoleEvents, setConsoleEvents] = useState([]);
+
+  const dispatchConsoleEvent = useCallback((messages) => {
+    setConsoleEvents(prev => [...prev, ...messages]);
+  }, []);
+
+  const clearConsole = useCallback(() => {
+    setConsoleEvents([]);
+  }, []);
+
+  return (
+    <ConsoleContext.Provider value={{ consoleEvents, dispatchConsoleEvent, clearConsole }}>
+      {children}
+    </ConsoleContext.Provider>
+  );
+}
+
+export function useConsole() {
+  const context = useContext(ConsoleContext);
+  if (!context) {
+    throw new Error('useConsole must be used within a ConsoleProvider');
+  }
+  return context;
+} 
\ No newline at end of file
diff --git a/client/modules/IDE/hooks/useHandleMessageEvent.js b/client/modules/IDE/hooks/useHandleMessageEvent.js
index 5603c1d697..7e9086a53d 100644
--- a/client/modules/IDE/hooks/useHandleMessageEvent.js
+++ b/client/modules/IDE/hooks/useHandleMessageEvent.js
@@ -1,10 +1,11 @@
 import { useDispatch } from 'react-redux';
 import { Decode } from 'console-feed';
-import { dispatchConsoleEvent } from '../actions/console';
 import { stopSketch, expandConsole } from '../actions/ide';
+import { useConsole } from '../context/ConsoleContext';
 
 export default function useHandleMessageEvent() {
   const dispatch = useDispatch();
+  const { dispatchConsoleEvent } = useConsole();
 
   const safeStringify = (
     obj,
@@ -62,7 +63,7 @@ export default function useHandleMessageEvent() {
       return;
     }
 
-    dispatch(dispatchConsoleEvent(decodedMessages));
+    dispatchConsoleEvent(decodedMessages);
   };
 
   return handleMessageEvent;
diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx
index a9cc72f169..d569ec2e96 100644
--- a/client/modules/IDE/pages/IDEView.jsx
+++ b/client/modules/IDE/pages/IDEView.jsx
@@ -10,6 +10,7 @@ import PreviewFrame from '../components/PreviewFrame';
 import Console from '../components/Console';
 import Toast from '../components/Toast';
 import { updateFileContent } from '../actions/files';
+import { ConsoleProvider } from '../context/ConsoleContext';
 
 import {
   autosaveProject,
@@ -167,127 +168,130 @@ const IDEView = () => {
     : consoleCollapsedSize;
 
   return (
-    <RootPage>
-      <Helmet>
-        <title>{getTitle(project)}</title>
-      </Helmet>
-      <IDEKeyHandlers getContent={() => cmRef.current?.getContent()} />
-      <WarnIfUnsavedChanges />
-      <Toast />
-      <P5VersionProvider>
-        <CmControllerContext.Provider value={cmRef}>
-          <Header syncFileContent={syncFileContent} />
-        </CmControllerContext.Provider>
-        {isMobile ? (
-          <>
-            <FloatingActionButton
-              syncFileContent={syncFileContent}
-              offsetBottom={ide.isPlaying ? currentConsoleSize : 0}
-            />
-            <PreviewWrapper show={ide.isPlaying}>
-              <SplitPane
-                style={{ position: 'static' }}
-                split="horizontal"
-                primary="second"
-                size={currentConsoleSize}
-                minSize={consoleCollapsedSize}
-                onChange={(size) => {
-                  setConsoleSize(size);
-                  setIsOverlayVisible(true);
-                }}
-                onDragFinished={() => {
-                  setIsOverlayVisible(false);
-                }}
-                allowResize={ide.consoleIsExpanded}
-                className="editor-preview-subpanel"
-              >
-                <PreviewFrame
-                  fullView
-                  hide={!ide.isPlaying}
-                  cmController={cmRef.current}
-                  isOverlayVisible={isOverlayVisible}
-                />
-                <Console />
-              </SplitPane>
-            </PreviewWrapper>
-            <EditorSidebarWrapper show={!ide.isPlaying}>
-              <Sidebar />
-              <Editor
-                provideController={(ctl) => {
-                  cmRef.current = ctl;
-                }}
+    <ConsoleProvider>
+      <RootPage>
+        <Helmet>
+          <title>{getTitle(project)}</title>
+        </Helmet>
+        <IDEKeyHandlers getContent={() => cmRef.current?.getContent()} />
+        <WarnIfUnsavedChanges />
+        <Toast />
+        <P5VersionProvider>
+          <CmControllerContext.Provider value={cmRef}>
+            <Header syncFileContent={syncFileContent} />
+          </CmControllerContext.Provider>
+          {isMobile ? (
+            <>
+              <FloatingActionButton
+                syncFileContent={syncFileContent}
+                offsetBottom={ide.isPlaying ? currentConsoleSize : 0}
               />
-            </EditorSidebarWrapper>
-          </>
-        ) : (
-          <main className="editor-preview-container">
-            <SplitPane
-              split="vertical"
-              size={ide.sidebarIsExpanded ? sidebarSize : 20}
-              onChange={(size) => {
-                setSidebarSize(size);
-              }}
-              allowResize={ide.sidebarIsExpanded}
-              minSize={150}
-            >
-              <Sidebar />
-              <SplitPane
-                split="vertical"
-                maxSize={MaxSize * 0.965}
-                defaultSize="50%"
-                onChange={() => {
-                  setIsOverlayVisible(true);
-                }}
-                onDragFinished={() => {
-                  setIsOverlayVisible(false);
-                }}
-                resizerStyle={{
-                  borderLeftWidth: '2px',
-                  borderRightWidth: '2px',
-                  width: '2px',
-                  margin: '0px 0px'
-                }}
-              >
+              <PreviewWrapper show={ide.isPlaying}>
                 <SplitPane
+                  style={{ position: 'static' }}
                   split="horizontal"
                   primary="second"
                   size={currentConsoleSize}
                   minSize={consoleCollapsedSize}
                   onChange={(size) => {
                     setConsoleSize(size);
+                    setIsOverlayVisible(true);
+                  }}
+                  onDragFinished={() => {
+                    setIsOverlayVisible(false);
                   }}
                   allowResize={ide.consoleIsExpanded}
                   className="editor-preview-subpanel"
                 >
-                  <Editor
-                    provideController={(ctl) => {
-                      cmRef.current = ctl;
-                    }}
+                  <PreviewFrame
+                    fullView
+                    hide={!ide.isPlaying}
+                    cmController={cmRef.current}
+                    isOverlayVisible={isOverlayVisible}
                   />
                   <Console />
                 </SplitPane>
-                <section className="preview-frame-holder">
-                  <header className="preview-frame__header">
-                    <h2 className="preview-frame__title">
-                      {t('Toolbar.Preview')}
-                    </h2>
-                  </header>
-                  <div className="preview-frame__content">
-                    <PreviewFrame
-                      cmController={cmRef.current}
-                      isOverlayVisible={isOverlayVisible}
+              </PreviewWrapper>
+              <EditorSidebarWrapper show={!ide.isPlaying}>
+                <Sidebar />
+                <Editor
+                  provideController={(ctl) => {
+                    cmRef.current = ctl;
+                  }}
+                />
+              </EditorSidebarWrapper>
+            </>
+          ) : (
+            <main className="editor-preview-container">
+              <SplitPane
+                split="vertical"
+                size={ide.sidebarIsExpanded ? sidebarSize : 20}
+                onChange={(size) => {
+                  setSidebarSize(size);
+                }}
+                allowResize={ide.sidebarIsExpanded}
+                minSize={150}
+              >
+                <Sidebar />
+                <SplitPane
+                  split="vertical"
+                  maxSize={MaxSize * 0.965}
+                  defaultSize="50%"
+                  onChange={() => {
+                    setIsOverlayVisible(true);
+                  }}
+                  onDragFinished={() => {
+                    setIsOverlayVisible(false);
+                  }}
+                  resizerStyle={{
+                    borderLeftWidth: '2px',
+                    borderRightWidth: '2px',
+                    width: '2px',
+                    margin: '0px 0px'
+                  }}
+                >
+                  <SplitPane
+                    split="horizontal"
+                    primary="second"
+                    size={currentConsoleSize}
+                    minSize={consoleCollapsedSize}
+                    onChange={(size) => {
+                      setConsoleSize(size);
+                    }}
+                    allowResize={ide.consoleIsExpanded}
+                    className="editor-preview-subpanel"
+                  >
+                    <Editor
+                      provideController={(ctl) => {
+                        cmRef.current = ctl;
+                      }}
                     />
-                  </div>
-                </section>
+                    <Console />
+                  </SplitPane>
+                  <section className="preview-frame-holder">
+                    <header className="preview-frame__header">
+                      <h2 className="preview-frame__title">
+                        {t('Toolbar.Preview')}
+                      </h2>
+                    </header>
+                    <div className="preview-frame__content">
+                      <PreviewFrame
+                        cmController={cmRef.current}
+                        isOverlayVisible={isOverlayVisible}
+                      />
+                    </div>
+                  </section>
+                </SplitPane>
               </SplitPane>
-            </SplitPane>
-          </main>
-        )}
-        <CmControllerContext.Provider value={cmRef}>
-          <IDEOverlays />
-        </CmControllerContext.Provider>
-      </P5VersionProvider>
-    </RootPage>
+            </main>
+          )}
+          <CmControllerContext.Provider value={cmRef}>
+            <IDEOverlays />
+          </CmControllerContext.Provider>
+        </P5VersionProvider>
+        <Console />
+      </RootPage>
+    </ConsoleProvider>
   );
 };