Skip to content

Commit f722f6a

Browse files
committed
Add code execution with piston and fix initial language bug
1 parent df3484b commit f722f6a

File tree

5 files changed

+102
-32
lines changed

5 files changed

+102
-32
lines changed

frontend/src/data/piston/PistonClient.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import axios from "axios";
2+
import { Language } from "domain/entities/Language";
23

34
const API_URL = "https://emkc.org/api/v2/piston";
45

56
const pistonAxios = axios.create({ baseURL: API_URL });
67

78
class PistonClient {
8-
static async executeCode(language: string, version: string, sourceCode: string) {
9-
const res = await pistonAxios.post("/execute", { language, version, files: [{ content: sourceCode }] });
9+
static async executeCode(language: Language, sourceCode: string) {
10+
const res = await pistonAxios.post("/execute", {
11+
language: language.language,
12+
version: language.version,
13+
files: [{ content: sourceCode }]
14+
});
15+
const output = res.data.run;
16+
return { stdout: output.stdout, stderr: output.stderr };
1017
}
1118

1219
static async getLanguageVersions() {

frontend/src/domain/context/CollaborationContext.tsx

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ interface CollaborationContextType {
1616
languages: Language[];
1717
handleChangeLanguage: (lang: Language) => void;
1818
handleExecuteCode: () => void;
19+
stdout: string;
20+
stderr: string;
1921
}
2022
const CollaborationContext = createContext<CollaborationContextType | undefined>(undefined);
2123

@@ -33,6 +35,10 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
3335
});
3436
const [languages, setLanguages] = useState<Language[]>([]);
3537

38+
const [stdout, setStdout] = useState<string>("");
39+
const [stderr, setStderr] = useState<string>("");
40+
const [isExecuting, setIsExecuting] = useState<boolean>(false);
41+
3642
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
3743
const monacoRef = useRef<Monaco | null>(null);
3844
const bindingRef = useRef<MonacoBinding | null>(null);
@@ -44,14 +50,20 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
4450
editorRef.current = editor;
4551
monacoRef.current = monaco;
4652

47-
await initialiseLanguages(monaco);
4853
const { yDoc, provider, yMap } = initialiseYdoc(roomId);
54+
4955
bindEditorToDoc(editor, yDoc, provider);
5056
setUpObserver(yMap);
5157
setUpConnectionAwareness(provider);
58+
await initialiseLanguages(monaco, yMap, editor);
5259
};
5360

54-
const initialiseLanguages = async (monaco: Monaco) => {
61+
const initialiseLanguages = async (
62+
monaco: Monaco,
63+
yMap: Y.Map<any>,
64+
editor: monaco.editor.IStandaloneCodeEditor
65+
) => {
66+
// Initialise language dropdown
5567
const allLanguages = monaco.languages.getLanguages();
5668
const pistonLanguageVersions = await PistonClient.getLanguageVersions();
5769
setLanguages(
@@ -63,6 +75,13 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
6375
version: pistonLanguageVersions.find((pistonLang: any) => pistonLang.language === lang.id)?.version
6476
}))
6577
);
78+
79+
// Set the editor's language
80+
const language: Language = yMap.get(SELECTED_LANGUAGE);
81+
const model = editor?.getModel();
82+
if (model) {
83+
monaco.editor.setModelLanguage(model, language?.language ?? "javascript");
84+
}
6685
};
6786

6887
const initialiseYdoc = (roomId: string): { yDoc: Y.Doc; yMap: Y.Map<any>; provider: WebsocketProvider } => {
@@ -98,9 +117,11 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
98117
yMap.observe((event) => {
99118
event.changes.keys.forEach((change, key) => {
100119
if (key === SELECTED_LANGUAGE) {
101-
const language = yMap.get(SELECTED_LANGUAGE);
102-
if (language) {
103-
setSelectedLanguage(language);
120+
const language: Language = yMap.get(SELECTED_LANGUAGE);
121+
setSelectedLanguage(language);
122+
const model = editorRef.current?.getModel();
123+
if (model) {
124+
monaco.editor.setModelLanguage(model, language.language);
104125
}
105126
}
106127
});
@@ -129,13 +150,36 @@ export const CollaborationProvider: React.FC<{ children: ReactNode }> = ({ child
129150
yMapRef.current?.set(SELECTED_LANGUAGE, lang);
130151
};
131152

132-
const handleExecuteCode = () => {
133-
const sourceCode = editorRef.current?.getValue();
153+
const handleExecuteCode = async () => {
154+
try {
155+
setIsExecuting(true);
156+
const sourceCode = editorRef.current?.getValue();
157+
if (!sourceCode) {
158+
// TODO
159+
return;
160+
}
161+
const output = await PistonClient.executeCode(selectedLanguage, sourceCode);
162+
const { stdout, stderr } = output;
163+
setStdout(stdout);
164+
setStderr(stderr);
165+
} catch (e) {
166+
toast.error("There was an issue running the code");
167+
} finally {
168+
setIsExecuting(false);
169+
}
134170
};
135171

136172
return (
137173
<CollaborationContext.Provider
138-
value={{ initialiseEditor, selectedLanguage, languages, handleChangeLanguage, handleExecuteCode }}
174+
value={{
175+
initialiseEditor,
176+
selectedLanguage,
177+
languages,
178+
handleChangeLanguage,
179+
handleExecuteCode,
180+
stdout,
181+
stderr
182+
}}
139183
>
140184
{children}
141185
</CollaborationContext.Provider>

frontend/src/presentation/components/CodeEditor/CodeEditor.tsx

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { PlayCircleOutlined, CloudUploadOutlined } from "@ant-design/icons";
77
import * as monaco from "monaco-editor";
88
import { SunOutlined, MoonFilled } from "@ant-design/icons";
99
import { Language } from "domain/entities/Language";
10+
import { LanguageSelector } from "./LanguageSelector";
1011

1112
interface CodeEditorProps {
1213
roomId: string;
@@ -19,21 +20,12 @@ interface LanguageOption {
1920
}
2021

2122
const CodeEditor: React.FC<CodeEditorProps> = ({ roomId }) => {
22-
const { initialiseEditor, languages, selectedLanguage, handleChangeLanguage, handleExecuteCode } =
23-
useCollaboration();
23+
const { initialiseEditor, handleExecuteCode } = useCollaboration();
2424
const [theme, setTheme] = useState("vs-light");
2525
const handleEditorDidMount = (editor: monaco.editor.IStandaloneCodeEditor, monaco: Monaco) => {
2626
initialiseEditor(roomId, editor, monaco);
2727
};
2828

29-
const languageOptions: LanguageOption[] = useMemo(() => {
30-
return languages.map((lang: Language) => ({
31-
label: lang.alias,
32-
value: lang.language,
33-
langData: lang
34-
}));
35-
}, [languages]);
36-
3729
const handleToggleTheme = () => {
3830
if (theme === "vs-light") {
3931
setTheme("vs-dark");
@@ -45,17 +37,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({ roomId }) => {
4537
return (
4638
<div className={styles.container}>
4739
<div className={styles.toolbar}>
48-
<Select
49-
variant="borderless"
50-
style={{ width: "150px" }}
51-
placeholder="Select language"
52-
options={languageOptions}
53-
value={selectedLanguage.language}
54-
onChange={(_, option) => {
55-
const langOption = option as LanguageOption;
56-
handleChangeLanguage(langOption.langData);
57-
}}
58-
/>
40+
<LanguageSelector />
5941
<div className={styles.buttonGroup}>
6042
<Button
6143
onClick={handleToggleTheme}
@@ -74,7 +56,6 @@ const CodeEditor: React.FC<CodeEditorProps> = ({ roomId }) => {
7456
<div className={styles.editor}>
7557
<Editor
7658
theme={theme}
77-
language={selectedLanguage.language}
7859
onMount={handleEditorDidMount}
7960
options={{
8061
minimap: { enabled: false },
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Select } from "antd";
2+
import { useCollaboration } from "domain/context/CollaborationContext";
3+
import { Language } from "domain/entities/Language";
4+
import { useMemo } from "react";
5+
6+
interface LanguageOption {
7+
label: string;
8+
value: string;
9+
langData: Language;
10+
}
11+
export const LanguageSelector: React.FC<{}> = () => {
12+
const { languages, selectedLanguage, handleChangeLanguage } = useCollaboration();
13+
const languageOptions: LanguageOption[] = useMemo(() => {
14+
return languages.map((lang: Language) => ({
15+
label: lang.alias,
16+
value: lang.language,
17+
langData: lang
18+
}));
19+
}, [languages]);
20+
21+
return (
22+
<Select
23+
variant="borderless"
24+
style={{ width: "150px" }}
25+
placeholder="Select language"
26+
options={languageOptions}
27+
value={selectedLanguage.language}
28+
onChange={(_, option) => {
29+
const langOption = option as LanguageOption;
30+
handleChangeLanguage(langOption.langData);
31+
}}
32+
/>
33+
);
34+
};

frontend/src/presentation/pages/CollaborationRoomPage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import { initialQuestions } from "data/repositories/mockQuestionRepository";
66
import { useParams } from "react-router-dom";
77
import { useResizable } from "react-resizable-layout";
88
import NotFound from "./NotFound";
9+
import { useCollaboration } from "domain/context/CollaborationContext";
910

1011
const CollaborationRoomPage: React.FC = () => {
1112
const { roomId } = useParams();
13+
const { stdout, stderr } = useCollaboration();
1214
const { position: questionPosition, separatorProps: verticalSeparatorProps } = useResizable({
1315
axis: "x",
1416
min: 300,
@@ -41,6 +43,8 @@ const CollaborationRoomPage: React.FC = () => {
4143
<div className={styles.horizontalSeparator} {...horizontalSeparatorProps} />
4244
<div className={styles.output} style={{ height: outputPosition }}>
4345
<p>Output</p>
46+
{stdout}
47+
{stderr}
4448
</div>
4549
</div>
4650
</div>

0 commit comments

Comments
 (0)