From ecd3cb55b49b0dac918e62b37de0f48c6cf53492 Mon Sep 17 00:00:00 2001
From: Sarah Rietkerk <49178322+srietkerk@users.noreply.github.com>
Date: Tue, 9 Jan 2024 11:29:46 -0800
Subject: [PATCH] Teacher tool: set up infrastructure to send messages to
iframe (#9811)
* makecodeframe only holds ui, message handling for iframe split into the makecodeeditor service, file made for the transform
* successfully sending message to set high contrast to the iframe when the evaluate button is pressed
* cleaned up a bit
* do something with the results of sending the message
* consolidated ref logic in the service, got rid of extra logic in editor container
* got rid of the editor container, it doesn't do anything meaningful anymore
* got rid of the load event listener
* changed ref handling, some minor changes in the service
* no longer exposing sendMessageAsync
* pushing messages to the queue even if the iframe isn't ready yet
* changed functions from anonymous to declarative
* fixed set editor ref logic, added check to see if message is undefined
* upgrade codeql analysis to v2
---
.github/workflows/codeql-analysis.yml | 2 +-
teachertool/src/App.tsx | 6 +-
teachertool/src/components/DebugInput.tsx | 4 +
.../src/components/EditorContainer.tsx | 32 -----
teachertool/src/components/MakecodeFrame.tsx | 130 +-----------------
.../src/services/makecodeEditorService.ts | 61 ++++++++
.../src/transforms/loadProjectAsync.ts | 12 ++
teachertool/src/utils/index.ts | 11 ++
8 files changed, 97 insertions(+), 161 deletions(-)
delete mode 100644 teachertool/src/components/EditorContainer.tsx
create mode 100644 teachertool/src/services/makecodeEditorService.ts
create mode 100644 teachertool/src/transforms/loadProjectAsync.ts
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index e89cb61b2418..29d3d8b9507f 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -66,4 +66,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
diff --git a/teachertool/src/App.tsx b/teachertool/src/App.tsx
index 5e77758ecc63..c72dc6607b10 100644
--- a/teachertool/src/App.tsx
+++ b/teachertool/src/App.tsx
@@ -7,9 +7,9 @@ import HeaderBar from "./components/HeaderBar";
import Notifications from "./components/Notifications";
import * as NotificationService from "./services/notificationService";
import { postNotification } from "./transforms/postNotification";
-import { makeNotification } from "./utils";
+import { createIFrameUrl, makeNotification } from "./utils";
import DebugInput from "./components/DebugInput";
-import { EditorContainer } from "./components/EditorContainer";
+import { MakeCodeFrame } from "./components/MakecodeFrame";
function App() {
@@ -36,7 +36,7 @@ function App() {
-
+
diff --git a/teachertool/src/components/DebugInput.tsx b/teachertool/src/components/DebugInput.tsx
index bc429f0f38ee..b3d5a43fd63d 100644
--- a/teachertool/src/components/DebugInput.tsx
+++ b/teachertool/src/components/DebugInput.tsx
@@ -4,6 +4,7 @@ import { useState } from "react";
import { Button } from "react-common/components/controls/Button";
import { Input } from "react-common/components/controls/Input";
import { Textarea } from "react-common/components/controls/Textarea";
+import { loadProjectAsync } from "../transforms/loadProjectAsync";
import { runEvaluateAsync } from "../transforms/runEvaluateAsync";
interface IProps {}
@@ -11,9 +12,12 @@ interface IProps {}
const DebugInput: React.FC = ({}) => {
const [shareLink, setShareLink] = useState("https://arcade.makecode.com/S50644-45891-08403-36583");
const [rubric, setRubric] = useState("");
+ const [bools, setBools] = useState(true);
const evaluate = async () => {
+ setBools(!bools);
await runEvaluateAsync(shareLink, rubric);
+ await loadProjectAsync("hi", bools);
}
return (
diff --git a/teachertool/src/components/EditorContainer.tsx b/teachertool/src/components/EditorContainer.tsx
deleted file mode 100644
index 5a92c83d57ca..000000000000
--- a/teachertool/src/components/EditorContainer.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { MakeCodeFrame } from "./MakecodeFrame";
-import { isLocal, getEditorUrl } from "../utils";
-
-const createIFrameUrl = (): string => {
- const editorUrl: string = isLocal() ? "http://localhost:3232/index.html#editor" : getEditorUrl((window as any).pxtTargetBundle.appTheme.embedUrl);
-
- let url = editorUrl
- if (editorUrl.charAt(editorUrl.length - 1) === "/" && !isLocal()) {
- url = editorUrl.substr(0, editorUrl.length - 1);
- }
- url += `?controller=1&ws=browser&nocookiebanner=1`;
- return url;
-}
-
-export const EditorContainer: React.FC<{}> = () => {
- const onIframeLoaded = () => {
- console.log("iframe loaded");
- }
-
- const onIframeClosed = () => {
- console.log("iframe closed");
- }
-
- return (
-
- )
-
-}
-
diff --git a/teachertool/src/components/MakecodeFrame.tsx b/teachertool/src/components/MakecodeFrame.tsx
index d54be39ddb92..25e3faaf5b82 100644
--- a/teachertool/src/components/MakecodeFrame.tsx
+++ b/teachertool/src/components/MakecodeFrame.tsx
@@ -1,141 +1,21 @@
///
import * as React from "react";
-import { useState, useEffect, useRef } from "react";
-import { ProgressBar } from "react-common/components/controls/ProgressBar";
+import { setEditorRef } from "../services/makecodeEditorService";
interface MakeCodeFrameProps {
pageSourceUrl: string;
- highContrast?: boolean;
- onFrameOpen: () => void;
- onFrameClose: () => void;
tutorialEventHandler?: (event: pxt.editor.EditorMessageTutorialEventRequest) => void;
}
-type FrameState = "loading" | "no-project" | "opening-project" | "project-open" | "closing-project";
-
-interface PendingMessage {
- original: pxt.editor.EditorMessageRequest;
- handler: (response: any) => void;
-}
-
export const MakeCodeFrame: React.FC =
- ( { pageSourceUrl,
- highContrast,
- onFrameClose,
- onFrameOpen
- } ) => {
- let ref: HTMLIFrameElement | undefined;
- const messageQueue: pxt.editor.EditorMessageRequest[] = [];
- let nextId: number = 0;
- let pendingMessages: {[index: string]: PendingMessage} = {};
- const [frameState, setFrameState] = useState("loading");
- const [loadPercent, setLoadPercent] = useState(0);
- const [workspaceReady, setWorkspaceReady] = useState(false);
-
- let frameRef = useRef(null);
-
- useEffect(() => {
- // logic we want to do when the iframe is loaded
- if (ref && ref.contentWindow) {
- window.addEventListener("message", onMessageReceived);
- ref.addEventListener("load", handleFrameReload)
-
- }
-
- // logic we want when the iframe unmounts
- return () => {
- window.removeEventListener("message", onMessageReceived);
- if (ref && ref.contentWindow) {
- ref.removeEventListener("load", handleFrameReload)
- }
- }
- }, [])
-
- useEffect(() => {
- sendMessageAsync({
- type: "pxteditor",
- action: "sethighcontrast",
- on: highContrast
- } as pxt.editor.EditorMessageSetHighContrastRequest);
- }, [highContrast])
-
-
- useEffect(() => {
- if (frameState === "project-open") {
- setFrameState("closing-project");
- onFrameClose();
- }
- else if (frameState === "no-project") {
- setFrameState("opening-project");
- onFrameOpen();
- }
- }, [frameState]);
-
-
- const handleFrameReload = () => {
- setFrameState("loading")
- }
-
- const onMessageReceived = (event: MessageEvent) => {
- const data = event.data as pxt.editor.EditorMessageRequest;
- if (frameState === "opening-project") setLoadPercent(Math.min((loadPercent || 0) + 7, 95));
-
- if (data.type === "pxteditor" && data.id && pendingMessages[data.id]) {
- const pending = pendingMessages[data.id];
- pending.handler(data);
- delete pendingMessages[data.id];
- return;
- }
-
- switch (data.action) {
- case "newproject":
- if (!workspaceReady) {
- setWorkspaceReady(true);
- sendMessageAsync(); // Flush message queue
- }
- if (frameState === "loading") {
- setFrameState("no-project");
- }
- break;
- default:
- console.log(JSON.stringify(data, null, 4));
- }
- }
-
- const sendMessageAsync = (message?: any) => {
- return new Promise(resolve => {
- const sendMessageCore = (message: any) => {
- message.response = true;
- message.id = nextId++ + "";
- pendingMessages[message.id] = {
- original: message,
- handler: resolve
- };
- ref!.contentWindow!.postMessage(message, "*");
- }
+ ( { pageSourceUrl} ) => {
- if (ref) {
- if (!workspaceReady) {
- messageQueue.push(message);
- }
- else {
- while (messageQueue.length) {
- sendMessageCore(messageQueue.shift());
- }
- if (message) sendMessageCore(message);
- }
- }
- });
+ const handleIFrameRef = (el: HTMLIFrameElement | null) => {
+ setEditorRef(el ?? undefined);
}
- const openingProject = frameState === "opening-project";
- const showLoader = openingProject || frameState === "closing-project";
/* eslint-disable @microsoft/sdl/react-iframe-missing-sandbox */
return
-
- {openingProject &&
}
-
{lf("Loading...")}
-
-
+
/* eslint-enable @microsoft/sdl/react-iframe-missing-sandbox */
}
\ No newline at end of file
diff --git a/teachertool/src/services/makecodeEditorService.ts b/teachertool/src/services/makecodeEditorService.ts
new file mode 100644
index 000000000000..1e88091ef359
--- /dev/null
+++ b/teachertool/src/services/makecodeEditorService.ts
@@ -0,0 +1,61 @@
+interface PendingMessage {
+ original: pxt.editor.EditorMessageRequest;
+ handler: (response: any) => void;
+}
+
+let makecodeEditorRef: HTMLIFrameElement | undefined;
+const messageQueue: pxt.editor.EditorMessageRequest[] = [];
+let nextId: number = 0;
+let pendingMessages: {[index: string]: PendingMessage} = {};
+
+function onMessageReceived(event: MessageEvent) {
+ const data = event.data as pxt.editor.EditorMessageRequest;
+ if (data.type === "pxteditor" && data.id && pendingMessages[data.id]) {
+ const pending = pendingMessages[data.id];
+ pending.handler(data);
+ delete pendingMessages[data.id];
+ return;
+ }
+
+ console.log("Received message from iframe:", data);
+}
+
+function sendMessageAsync(message?: any) {
+ return new Promise(resolve => {
+ const sendMessageCore = (message: any) => {
+ message.response = true;
+ message.id = nextId++ + "";
+ pendingMessages[message.id] = {
+ original: message,
+ handler: resolve
+ };
+ makecodeEditorRef!.contentWindow!.postMessage(message, "*");
+ }
+
+ if (message) messageQueue.push(message);
+ if (makecodeEditorRef) {
+ while (messageQueue.length) {
+ sendMessageCore(messageQueue.shift());
+ }
+ }
+ });
+}
+
+export function setEditorRef(ref: HTMLIFrameElement | undefined) {
+ makecodeEditorRef = ref ?? undefined;
+ window.removeEventListener("message", onMessageReceived);
+ if (ref) {
+ window.addEventListener("message", onMessageReceived);
+ sendMessageAsync();
+ }
+}
+
+// an example of events that we want to/can send to the editor
+export async function setHighContrastAsync(on: boolean) {
+ const result = await sendMessageAsync({
+ type: "pxteditor",
+ action: "sethighcontrast",
+ on: on
+ });
+ console.log(result);
+}
diff --git a/teachertool/src/transforms/loadProjectAsync.ts b/teachertool/src/transforms/loadProjectAsync.ts
new file mode 100644
index 000000000000..272e5e4fccf7
--- /dev/null
+++ b/teachertool/src/transforms/loadProjectAsync.ts
@@ -0,0 +1,12 @@
+import { stateAndDispatch } from "../state";
+import * as Actions from "../state/actions";
+import { postNotification } from "./postNotification";
+import { makeNotification } from "../utils";
+import { setHighContrastAsync } from "../services/makecodeEditorService";
+
+export async function loadProjectAsync(projectId: string, bool: boolean) {
+ const { dispatch } = stateAndDispatch();
+ await setHighContrastAsync(bool);
+ postNotification(makeNotification(`project ${projectId} evaluated`, 2000));
+
+}
diff --git a/teachertool/src/utils/index.ts b/teachertool/src/utils/index.ts
index 8623f94812c9..7a65a43e2afe 100644
--- a/teachertool/src/utils/index.ts
+++ b/teachertool/src/utils/index.ts
@@ -28,4 +28,15 @@ export const getEditorUrl = (embedUrl: string) => {
// example: https://arcade.makecode.com/abc123 and this would get returned
const path = /\/([\da-zA-Z\.]+)(?:--)?/i.exec(window.location.pathname);
return `${embedUrl.replace(/\/$/, "")}/${path?.[1] || ""}`;
+}
+
+export const createIFrameUrl = (): string => {
+ const editorUrl: string = isLocal() ? "http://localhost:3232/index.html#editor" : getEditorUrl((window as any).pxtTargetBundle.appTheme.embedUrl);
+
+ let url = editorUrl
+ if (editorUrl.charAt(editorUrl.length - 1) === "/" && !isLocal()) {
+ url = editorUrl.substr(0, editorUrl.length - 1);
+ }
+ url += `?controller=1&ws=browser&nocookiebanner=1`;
+ return url;
}
\ No newline at end of file