Skip to content
Merged
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
133 changes: 99 additions & 34 deletions frontend/src/components/Buttons/PlayPause.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from "react";
import React, { RefObject, useEffect, useRef, useState } from "react";
import { StyledHeaderButton } from "BtStyles/Header/HeaderMenu.styles";
import { Entry, useError } from "jderobot-ide-interface";
import { CommsManager, states } from "jderobot-commsmanager";
Expand All @@ -11,12 +11,26 @@ import { publish, subscribe, unsubscribe, zipCodeFiles } from "BtHelpers/utils";
import { LoadingIcon, PauseIcon, PlayIcon } from "BtIcons";
import { useProjectSettings } from "BtContexts/ProjectSettingsContext";

const PlayPauseButton = ({ project }: { project: string }) => {
const PlayPauseButton = ({
project,
supportedLanguages,
userRef,
entrypointRef,
additionalEntrypoints,
}: {
project: string;
supportedLanguages: string[];
userRef?: RefObject<string | undefined>;
entrypointRef: RefObject<Entry | undefined>;
additionalEntrypoints?: string[];
}) => {
const settings = useProjectSettings();
const theme = useBtTheme();
const { warning, error } = useError();
const filesRef = useRef<Entry[]>([]);
const runningFilesRef = useRef<JSZip>(JSZip);
const runningEntrypointRef = useRef<Entry | undefined>(undefined);
const runningContentRef = useRef<string | undefined>(undefined);
const [state, setState] = useState<string>(states.IDLE);
const [loading, setLoading] = useState<boolean>(false);
const isCodeUpdatedRef = useRef<boolean | undefined>(undefined);
Expand Down Expand Up @@ -56,6 +70,26 @@ const PlayPauseButton = ({ project }: { project: string }) => {
}
}, [state]);

const getLanguage = (extension?: string) => {
const fileTypes = {
py: "python",
cpp: "cpp",
json: "tree",
};

if (extension === undefined) {
return undefined;
}

for (const key in fileTypes) {
if (key === extension) {
return fileTypes[key as keyof typeof fileTypes];
}
}

return undefined;
};

const compareZips = async (zip1: JSZip, zip2: JSZip) => {
for (const key in zip1.files) {
if (!Object.hasOwn(zip1.files, key)) continue;
Expand Down Expand Up @@ -114,6 +148,25 @@ const PlayPauseButton = ({ project }: { project: string }) => {
return;
}

if (entrypointRef.current === undefined) {
error(
"Failed to run the application. Make sure to select an entrypoint by opening it in the editor.",
);
setLoading(false);
return;
}

const language = getLanguage(entrypointRef.current.path.split(".").pop());

if (language === undefined || !supportedLanguages.includes(language)) {
console.log(language);
error(
`Failed to run the application. Entrypoint ${entrypointRef.current.path} is not supported.`,
);
setLoading(false);
return;
}

if (save === undefined) {
publish("autoSave");
updateCode(false);
Expand All @@ -126,11 +179,11 @@ const PlayPauseButton = ({ project }: { project: string }) => {

const files = await getFileList(project);
filesRef.current = JSON.parse(files);
const userZip = await loadFiles(filesRef.current);
const userZip = await loadFiles(entrypointRef.current, filesRef.current);

if (state === states.PAUSED) {
const sameZips = await compareZips(userZip, runningFilesRef.current);
if (sameZips) {
if (sameZips && runningEntrypointRef.current === entrypointRef.current) {
try {
await manager.resume();
console.log("App resumed correctly!");
Expand All @@ -148,14 +201,19 @@ const PlayPauseButton = ({ project }: { project: string }) => {
try {
runningFilesRef.current = userZip;
const helperZip = new JSZip();
// Get the blob from the API wrapper
const appFiles = await generateDockerizedApp(
project,
settings.btOrder.value,
);
helperZip.file("self_contained_tree.xml", appFiles.tree);
TreeGardener.addDockerFiles(helperZip);
RosTemplates.addDockerFiles(helperZip);
runningEntrypointRef.current = entrypointRef.current;

// TODO: only if entrypoint is json
if (language === "tree") {
// Get the blob from the API wrapper
const appFiles = await generateDockerizedApp(
project,
settings.btOrder.value,
);
helperZip.file("self_contained_tree.xml", appFiles.tree);
TreeGardener.addDockerFiles(helperZip);
RosTemplates.addDockerFiles(helperZip);
}

const finalZip = await mergeZips(helperZip, userZip);

Expand All @@ -164,13 +222,27 @@ const PlayPauseButton = ({ project }: { project: string }) => {
reader.onloadend = async () => {
const base64data = reader.result; // Get the zip in base64
// Send the base64 encoded blob
if (base64data) {
if (base64data && runningEntrypointRef.current) {
const entrypoints =
language === "tree"
? ["/workspace/code/execute_docker.py"]
: [`/workspace/code/${runningEntrypointRef.current.path}`];
if (additionalEntrypoints) {
additionalEntrypoints.forEach((entrypoint) => {
entrypoints.push(`/workspace/code/${entrypoint}`);
});
}

let to_lint = [];
if (language === "tree") {
to_lint = ["actions/*.py"];
} else {
to_lint = additionalEntrypoints ? additionalEntrypoints : [];
to_lint = [runningEntrypointRef.current.path].concat(to_lint);
}

try {
await manager.run(
"/workspace/code/execute_docker.py",
["actions/*.py"],
base64data as string,
);
await manager.run(entrypoints, to_lint, base64data as string);
} catch {
error(
"Failed to run the application. See the traces in the terminal.",
Expand All @@ -193,25 +265,18 @@ const PlayPauseButton = ({ project }: { project: string }) => {
error("Error running app: " + e.message);
}
}
};

async function loadFiles(files: Entry[]) {
const zip = new JSZip();
async function loadFiles(entrypoint: Entry, files: Entry[]) {
const zip = new JSZip();

let actions = undefined;
for (const file of filesRef.current) {
if (file.is_dir && file.name === "actions") {
actions = file;
}
}
await zipCodeFiles(zip, files, project);

if (actions === undefined) {
throw Error("Action directory not found");
}

await zipCodeFiles(zip, files, project);
return zip;
}
};
zip.files[entrypoint.path]._data.then(
(value: string) => (runningContentRef.current = value),
);
return zip;
}

return (
<StyledHeaderButton
Expand Down
29 changes: 26 additions & 3 deletions frontend/src/components/HeaderMenu/HeaderMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { RefObject, useRef } from "react";
import { useEffect, useState } from "react";
import AppBar from "@mui/material/AppBar";
import Toolbar from "@mui/material/Toolbar";
Expand All @@ -24,7 +24,7 @@ import {
} from "BtStyles/Header/HeaderMenu.styles";
import { useBtTheme } from "BtContexts/BtThemeContext";
import { getProjectInfo } from "BtApi/TreeWrapper";
import { Layout } from "jderobot-ide-interface";
import { Entry, Layout } from "jderobot-ide-interface";
import { CommsManager } from "jderobot-commsmanager";
import { subscribe, unsubscribe } from "BtHelpers/utils";
import ConnectButton from "BtComponents/Buttons/Connect";
Expand Down Expand Up @@ -96,8 +96,10 @@ const HeaderMenu = ({
{/* <SettingsButton project={project} /> */}
<ExecutionControl
project={project}
supportedLanguages={["cpp", "python", "tree"]}
commsManager={commsManager}
connectManager={connectManager}
additionalEntrypoints={[]}
/>
<DocumentationButton />
</StyledHeaderButtonContainer>
Expand All @@ -108,19 +110,26 @@ const HeaderMenu = ({

const ExecutionControl = ({
project,
supportedLanguages,
commsManager,
connectManager,
userRef,
additionalEntrypoints,
}: {
project: string;
supportedLanguages: string[];
commsManager: CommsManager | null;
userRef?: RefObject<string | undefined>;
connectManager: (
desiredState?: string,
callback?: () => void,
) => Promise<void>;
additionalEntrypoints?: string[];
}) => {
const [state, setState] = useState<string | undefined>(
commsManager?.getState(),
);
const entrypointRef = useRef<Entry | undefined>(undefined);

const updateState = (e: unknown) => {
const T = CustomEvent<{ detail: unknown }>;
Expand All @@ -129,11 +138,20 @@ const ExecutionControl = ({
}
};

const updateCurrent = (e: unknown) => {
const T = CustomEvent<{ detail: { file?: Entry } }>;
if (e instanceof T) {
entrypointRef.current = e.detail.file;
}
};

useEffect(() => {
subscribe("CommsManagerStateChange", updateState);
subscribe("currentFile", updateCurrent);

return () => {
unsubscribe("CommsManagerStateChange", () => {});
unsubscribe("currentFile", () => {});
};
}, []);

Expand All @@ -145,7 +163,12 @@ const ExecutionControl = ({
<ConnectButton connectManager={connectManager} />
) : (
<>
<PlayPauseButton project={project} />
<PlayPauseButton
project={project}
supportedLanguages={supportedLanguages}
entrypointRef={entrypointRef}
additionalEntrypoints={additionalEntrypoints}
/>
<ResetButton />
<TerminateWorldButton />
</>
Expand Down
Loading