Implement Code Challenge component with styling and WebSocket integration
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import VSCodeUI from "./components/VSCodeUI.jsx"
|
import CodeChallenge from "./components/CodeChallenge.jsx"
|
||||||
import "./index.css"
|
import "./index.css"
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<VSCodeUI />
|
<CodeChallenge />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
282
Frontend/src/components/CodeChallenge.css
Normal file
282
Frontend/src/components/CodeChallenge.css
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
/* Code Challenge Component Styles */
|
||||||
|
.code-challenge-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-challenge-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-challenge-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-in-btn {
|
||||||
|
background-color: #333;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-in-btn:hover {
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-challenge-problem-nav {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-number {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-challenge-main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: calc(70vh - 120px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 50%;
|
||||||
|
border-right: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-tabs {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-tabs button {
|
||||||
|
min-width: 120px;
|
||||||
|
padding: 0 20px;
|
||||||
|
background-color: #252526;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: none;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-right: 1px solid #333;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-tabs button.tab-active {
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
border-left: 2px solid #0078d4;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-tabs button:hover:not(.tab-active) {
|
||||||
|
background-color: #252526;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
border-right: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-container h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-description {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-description code {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Consolas', 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Removed problem examples styles */
|
||||||
|
|
||||||
|
.editor-section {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-left: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #252526;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-selector {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: 1px solid #3c3c3c;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-btn {
|
||||||
|
background-color: #252526;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: 1px solid #3c3c3c;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-selected {
|
||||||
|
background-color: #0e639c;
|
||||||
|
border-color: #0e639c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #252526;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: 1px solid #3c3c3c;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #0e8a16;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-btn svg, .submit-btn svg {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-btn:hover {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
background-color: #0ca01c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-section {
|
||||||
|
height: 30vh;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
border-top: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background-color: #252526;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-content {
|
||||||
|
padding: 10px;
|
||||||
|
font-family: 'Consolas', 'Courier New', monospace;
|
||||||
|
height: calc(30vh - 40px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line.system {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line.output {
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line.error {
|
||||||
|
color: #f48771;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-prompt {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-symbol {
|
||||||
|
margin-right: 5px;
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-input {
|
||||||
|
flex: 1;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-family: 'Consolas', 'Courier New', monospace;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
451
Frontend/src/components/CodeChallenge.jsx
Normal file
451
Frontend/src/components/CodeChallenge.jsx
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import Editor from "@monaco-editor/react";
|
||||||
|
import { Play, Send } from 'lucide-react';
|
||||||
|
|
||||||
|
const CodeChallenge = () => {
|
||||||
|
const [activeQuestion, setActiveQuestion] = useState("Q.1");
|
||||||
|
const [language, setLanguage] = useState("JavaScript");
|
||||||
|
const [code, setCode] = useState("");
|
||||||
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
|
const [terminalOutput, setTerminalOutput] = useState([]);
|
||||||
|
const [autoSelected, setAutoSelected] = useState(true);
|
||||||
|
const [activeSocket, setActiveSocket] = useState(null);
|
||||||
|
const [submissionId, setSubmissionId] = useState(null);
|
||||||
|
const socketRef = useRef(null);
|
||||||
|
|
||||||
|
// Example problem data
|
||||||
|
const problems = {
|
||||||
|
"Q.1": {
|
||||||
|
id: "two-sum",
|
||||||
|
title: "Two Sum",
|
||||||
|
description: "Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.",
|
||||||
|
constraints: "You may assume that each input would have exactly one solution, and you may not use the same element twice.",
|
||||||
|
examples: [
|
||||||
|
{
|
||||||
|
input: "nums = [2,7,11,15], target = 9",
|
||||||
|
output: "[0,1]",
|
||||||
|
explanation: "Because nums[0] + nums[1] == 9, we return [0, 1]."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "nums = [3,2,4], target = 6",
|
||||||
|
output: "[1,2]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "nums = [3,3], target = 6",
|
||||||
|
output: "[0,1]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
starterCode: `/**
|
||||||
|
* @param {number[]} nums
|
||||||
|
* @param {number} target
|
||||||
|
* @return {number[]}
|
||||||
|
*/
|
||||||
|
var twoSum = function(nums, target) {
|
||||||
|
// Write your solution here
|
||||||
|
|
||||||
|
};`
|
||||||
|
},
|
||||||
|
"Q.2": {
|
||||||
|
id: "palindrome-number",
|
||||||
|
title: "Palindrome Number",
|
||||||
|
description: "Given an integer x, return true if x is a palindrome, and false otherwise.",
|
||||||
|
examples: [
|
||||||
|
{
|
||||||
|
input: "x = 121",
|
||||||
|
output: "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "x = -121",
|
||||||
|
output: "false",
|
||||||
|
explanation: "From left to right, it reads -121. From right to left, it reads 121-. Therefore it is not a palindrome."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
starterCode: `/**
|
||||||
|
* @param {number} x
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
var isPalindrome = function(x) {
|
||||||
|
// Write your solution here
|
||||||
|
|
||||||
|
};`
|
||||||
|
},
|
||||||
|
"Q.3": {
|
||||||
|
id: "valid-parentheses",
|
||||||
|
title: "Valid Parentheses",
|
||||||
|
description: "Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid.",
|
||||||
|
constraints: "An input string is valid if: Open brackets must be closed by the same type of brackets. Open brackets must be closed in the correct order.",
|
||||||
|
examples: [
|
||||||
|
{
|
||||||
|
input: 's = "()"',
|
||||||
|
output: "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 's = "()[]{}"',
|
||||||
|
output: "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 's = "(]"',
|
||||||
|
output: "false"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
starterCode: `/**
|
||||||
|
* @param {string} s
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
var isValid = function(s) {
|
||||||
|
// Write your solution here
|
||||||
|
|
||||||
|
};`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set initial code based on active problem
|
||||||
|
useEffect(() => {
|
||||||
|
if (problems[activeQuestion]) {
|
||||||
|
setCode(problems[activeQuestion].starterCode);
|
||||||
|
}
|
||||||
|
}, [activeQuestion]);
|
||||||
|
|
||||||
|
// Cleanup WebSocket connection on unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (socketRef.current) {
|
||||||
|
socketRef.current.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Connect to WebSocket
|
||||||
|
const connectToWebSocket = (id) => {
|
||||||
|
// Close existing connection if any
|
||||||
|
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
|
||||||
|
socketRef.current.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsUrl = `ws://localhost:8080/api/ws/terminal/${id}`;
|
||||||
|
const socket = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
console.log('WebSocket connection established');
|
||||||
|
setActiveSocket(socket);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
|
||||||
|
switch (message.type) {
|
||||||
|
case 'output':
|
||||||
|
setTerminalOutput(prev => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
type: message.content.isError ? 'error' : 'output',
|
||||||
|
content: message.content.text
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
setTerminalOutput(prev => [
|
||||||
|
...prev,
|
||||||
|
{ type: 'system', content: `Status: ${message.content.status}` }
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (message.content.status === 'completed' || message.content.status === 'failed') {
|
||||||
|
setIsRunning(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
setTerminalOutput(prev => [
|
||||||
|
...prev,
|
||||||
|
{ type: 'error', content: message.content.message }
|
||||||
|
]);
|
||||||
|
setIsRunning(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'system':
|
||||||
|
setTerminalOutput(prev => [
|
||||||
|
...prev,
|
||||||
|
{ type: 'system', content: message.content }
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log('Unknown message type:', message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing WebSocket message:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = (error) => {
|
||||||
|
console.error('WebSocket error:', error);
|
||||||
|
setTerminalOutput(prev => [
|
||||||
|
...prev,
|
||||||
|
{ type: 'error', content: 'WebSocket connection error' }
|
||||||
|
]);
|
||||||
|
setIsRunning(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onclose = () => {
|
||||||
|
console.log('WebSocket connection closed');
|
||||||
|
setActiveSocket(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
socketRef.current = socket;
|
||||||
|
return socket;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle code execution
|
||||||
|
const runCode = async () => {
|
||||||
|
setIsRunning(true);
|
||||||
|
setTerminalOutput([
|
||||||
|
{ type: 'system', content: `Running ${problems[activeQuestion].id}...` }
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Submit code to the backend
|
||||||
|
const response = await fetch('http://localhost:8080/api/submit', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
code: code,
|
||||||
|
language: language.toLowerCase(),
|
||||||
|
input: '', // Add input if needed
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setSubmissionId(data.id);
|
||||||
|
|
||||||
|
// Connect to WebSocket for real-time updates
|
||||||
|
connectToWebSocket(data.id);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting code:', error);
|
||||||
|
setTerminalOutput(prev => [
|
||||||
|
...prev,
|
||||||
|
{ type: 'error', content: `Error: ${error.message}` }
|
||||||
|
]);
|
||||||
|
setIsRunning(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle code submission
|
||||||
|
const submitCode = async () => {
|
||||||
|
setIsRunning(true);
|
||||||
|
setTerminalOutput([
|
||||||
|
{ type: 'system', content: `Submitting solution for ${problems[activeQuestion].id}...` }
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Submit code to the backend
|
||||||
|
const response = await fetch('http://localhost:8080/api/submit', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
code: code,
|
||||||
|
language: language.toLowerCase(),
|
||||||
|
input: '',
|
||||||
|
problemId: problems[activeQuestion].id
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setSubmissionId(data.id);
|
||||||
|
|
||||||
|
// Connect to WebSocket for real-time updates
|
||||||
|
connectToWebSocket(data.id);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error submitting solution:', error);
|
||||||
|
setTerminalOutput(prev => [
|
||||||
|
...prev,
|
||||||
|
{ type: 'error', content: `Error: ${error.message}` }
|
||||||
|
]);
|
||||||
|
setIsRunning(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the current problem
|
||||||
|
const renderProblem = () => {
|
||||||
|
const problem = problems[activeQuestion];
|
||||||
|
if (!problem) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="problem-container">
|
||||||
|
<h1>{problem.title}</h1>
|
||||||
|
|
||||||
|
<div className="problem-description">
|
||||||
|
<p>{problem.description}</p>
|
||||||
|
{problem.constraints && <p>{problem.constraints}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Test cases section removed */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="code-challenge-container">
|
||||||
|
<header className="code-challenge-header">
|
||||||
|
<h1>OnScreen Test</h1>
|
||||||
|
<button className="sign-in-btn">Sign In</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="code-challenge-problem-nav">
|
||||||
|
<h3 className="problem-number">1. {problems["Q.1"].title}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="code-challenge-main">
|
||||||
|
<div className="problem-tabs">
|
||||||
|
<button
|
||||||
|
className={activeQuestion === "Q.1" ? "tab-active" : ""}
|
||||||
|
onClick={() => setActiveQuestion("Q.1")}
|
||||||
|
>
|
||||||
|
Q.1
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={activeQuestion === "Q.2" ? "tab-active" : ""}
|
||||||
|
onClick={() => setActiveQuestion("Q.2")}
|
||||||
|
>
|
||||||
|
Q.2
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={activeQuestion === "Q.3" ? "tab-active" : ""}
|
||||||
|
onClick={() => setActiveQuestion("Q.3")}
|
||||||
|
>
|
||||||
|
Q.3
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="problem-content">
|
||||||
|
{renderProblem()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="editor-section">
|
||||||
|
<div className="editor-header">
|
||||||
|
<div className="editor-controls">
|
||||||
|
<select
|
||||||
|
value={language}
|
||||||
|
onChange={(e) => setLanguage(e.target.value)}
|
||||||
|
className="language-selector"
|
||||||
|
>
|
||||||
|
<option value="JavaScript">JavaScript</option>
|
||||||
|
<option value="Python">Python</option>
|
||||||
|
<option value="Java">Java</option>
|
||||||
|
<option value="C++">C++</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`auto-btn ${autoSelected ? 'auto-selected' : ''}`}
|
||||||
|
onClick={() => setAutoSelected(!autoSelected)}
|
||||||
|
>
|
||||||
|
Auto
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="editor-actions">
|
||||||
|
<button
|
||||||
|
className="run-btn"
|
||||||
|
onClick={runCode}
|
||||||
|
disabled={isRunning}
|
||||||
|
>
|
||||||
|
<Play size={16} /> Run
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="submit-btn"
|
||||||
|
onClick={submitCode}
|
||||||
|
disabled={isRunning}
|
||||||
|
>
|
||||||
|
<Send size={16} /> Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="editor-container">
|
||||||
|
<Editor
|
||||||
|
height="100%"
|
||||||
|
defaultLanguage="javascript"
|
||||||
|
language={language.toLowerCase()}
|
||||||
|
value={code}
|
||||||
|
onChange={(value) => setCode(value)}
|
||||||
|
theme="vs-dark"
|
||||||
|
options={{
|
||||||
|
fontSize: 14,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
automaticLayout: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="terminal-section">
|
||||||
|
<div className="terminal-header">
|
||||||
|
<span>Terminal</span>
|
||||||
|
<div className="terminal-controls">
|
||||||
|
<button className="terminal-btn">⊞</button>
|
||||||
|
<button className="terminal-btn">□</button>
|
||||||
|
<button className="terminal-btn">✕</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="terminal-content">
|
||||||
|
{terminalOutput.map((line, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`terminal-line ${line.type}`}
|
||||||
|
>
|
||||||
|
{line.content}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="terminal-prompt">
|
||||||
|
<span className="prompt-symbol">$</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="terminal-input"
|
||||||
|
placeholder="Type here..."
|
||||||
|
disabled={!isRunning}
|
||||||
|
onKeyPress={(e) => {
|
||||||
|
if (e.key === 'Enter' && activeSocket) {
|
||||||
|
const input = e.target.value;
|
||||||
|
// Send input to WebSocket
|
||||||
|
activeSocket.send(JSON.stringify({
|
||||||
|
type: 'input',
|
||||||
|
content: { text: input }
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Add input to terminal output
|
||||||
|
setTerminalOutput(prev => [
|
||||||
|
...prev,
|
||||||
|
{ type: 'system', content: `$ ${input}` }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Clear the input field
|
||||||
|
e.target.value = '';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeChallenge;
|
||||||
@@ -1026,3 +1026,285 @@ body {
|
|||||||
.panel-close-btn:hover {
|
.panel-close-btn:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Code Challenge Component Styles */
|
||||||
|
.code-challenge-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: var(--vscode-background);
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-challenge-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background-color: var(--vscode-background);
|
||||||
|
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-challenge-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-in-btn {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border: 1px solid rgba(128, 128, 128, 0.5);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-in-btn:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-challenge-problem-nav {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-number {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-challenge-main {
|
||||||
|
display: flex;
|
||||||
|
height: 60vh;
|
||||||
|
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 130px;
|
||||||
|
border-right: 1px solid rgba(128, 128, 128, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-tabs button {
|
||||||
|
padding: 16px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid rgba(128, 128, 128, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-tabs button.tab-active {
|
||||||
|
background-color: var(--vscode-background);
|
||||||
|
font-weight: 500;
|
||||||
|
border-left: 2px solid #007acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-tabs button:hover:not(.tab-active) {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 16px;
|
||||||
|
border-right: 1px solid rgba(128, 128, 128, 0.35);
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-container h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-description {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-examples h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-box {
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-section {
|
||||||
|
width: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #252526;
|
||||||
|
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-selector {
|
||||||
|
background-color: #3c3c3c;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: 1px solid #3c3c3c;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-btn {
|
||||||
|
background-color: #3c3c3c;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auto-selected {
|
||||||
|
background-color: #4d4d4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
background-color: #3c3c3c;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
background-color: #0e639c;
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-btn:hover {
|
||||||
|
background-color: #4d4d4d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
background-color: #1177bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-container {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-section {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--vscode-panel-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 12px;
|
||||||
|
background-color: #252526;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-btn {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line.system {
|
||||||
|
color: #569cd6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-line.error {
|
||||||
|
color: #f48771;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-prompt {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-symbol {
|
||||||
|
color: #569cd6;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-input {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
flex: 1;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user