Enhance CodeChallenge component with improved styling for problem examples and refined WebSocket handling for execution state management

This commit is contained in:
ishikabhoyar
2025-07-23 16:04:06 +05:30
parent 6d77a6b8ce
commit 2867f3bf42
2 changed files with 292 additions and 69 deletions

View File

@@ -115,7 +115,21 @@
font-family: 'Consolas', 'Courier New', monospace; font-family: 'Consolas', 'Courier New', monospace;
} }
/* Removed problem examples styles */ .problem-examples h2 {
font-size: 18px;
margin-top: 20px;
margin-bottom: 10px;
}
.example-box {
background-color: #161616;
border-radius: 5px;
padding: 15px;
margin-bottom: 15px;
line-height: 1.5;
font-family: 'Consolas', 'Courier New', monospace;
border-left: 3px solid #444;
}
.editor-section { .editor-section {
flex: 1; flex: 1;

View File

@@ -16,14 +16,34 @@ const CodeChallenge = () => {
// Map frontend language names to backend language identifiers // Map frontend language names to backend language identifiers
const getLanguageIdentifier = (uiLanguage) => { const getLanguageIdentifier = (uiLanguage) => {
const languageMap = { const languageMap = {
'javascript': 'javascript',
'python': 'python', 'python': 'python',
'java': 'java', 'java': 'java',
'c++': 'cpp', 'c++': 'cpp',
'c': 'c' 'c': 'c'
}; };
// Important: make sure we convert to lowercase to match the backend's expected format
return languageMap[uiLanguage.toLowerCase()] || uiLanguage.toLowerCase(); return languageMap[uiLanguage.toLowerCase()] || uiLanguage.toLowerCase();
}; };
// Reset execution state to allow rerunning
const resetExecutionState = () => {
setIsRunning(false);
// Properly close the socket if it exists and is open
if (socketRef.current) {
if (socketRef.current.readyState === WebSocket.OPEN) {
socketRef.current.close();
}
socketRef.current = null;
}
// Ensure activeSocket is also nullified
setActiveSocket(null);
console.log('Execution state reset, buttons should be enabled');
};
// Example problem data // Example problem data
const problems = { const problems = {
"Q.1": { "Q.1": {
@@ -112,13 +132,9 @@ var isValid = function(s) {
// Get appropriate starter code based on language // Get appropriate starter code based on language
const getStarterCode = (problem, lang) => { const getStarterCode = (problem, lang) => {
// Default JavaScript starter code is in the problem object
if (lang === 'JavaScript') {
return problem.starterCode;
}
// Language-specific starter code templates // Language-specific starter code templates
const templates = { const templates = {
'JavaScript': problem.starterCode,
'C': `#include <stdio.h> 'C': `#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h> #include <stdbool.h>
@@ -133,6 +149,8 @@ int main() {
'Python': `# ${problem.title} 'Python': `# ${problem.title}
def solution(): def solution():
# Write your solution here # Write your solution here
# Use input() for user input in Python
# Example: name = input("Enter your name: ")
pass pass
if __name__ == "__main__": if __name__ == "__main__":
@@ -175,13 +193,46 @@ int main() {
}; };
}, []); }, []);
// Connect to WebSocket // Set a safety timeout to ensure buttons are re-enabled if execution hangs
const connectToWebSocket = (id) => { useEffect(() => {
// Close existing connection if any let safetyTimer = null;
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
socketRef.current.close(); if (isRunning) {
// If execution is running for more than 30 seconds, reset state
safetyTimer = setTimeout(() => {
console.log('Safety timeout reached, re-enabling buttons');
resetExecutionState();
}, 30000);
} }
return () => {
if (safetyTimer) clearTimeout(safetyTimer);
};
}, [isRunning]);
// Connect to WebSocket
const connectToWebSocket = (id) => {
console.log('Connecting to WebSocket with ID:', id);
// Force close any existing connections
if (socketRef.current) {
console.log('Closing existing socket, state:', socketRef.current.readyState);
socketRef.current.onclose = null; // Remove existing handler to avoid conflicts
socketRef.current.onerror = null;
socketRef.current.onmessage = null;
if (socketRef.current.readyState !== WebSocket.CLOSED) {
socketRef.current.close();
}
socketRef.current = null;
}
if (activeSocket) {
console.log('Clearing active socket reference');
setActiveSocket(null);
}
console.log('Creating new WebSocket connection');
const wsUrl = `ws://localhost:8080/api/ws/terminal/${id}`; const wsUrl = `ws://localhost:8080/api/ws/terminal/${id}`;
const socket = new WebSocket(wsUrl); const socket = new WebSocket(wsUrl);
@@ -193,7 +244,7 @@ int main() {
socket.onmessage = (event) => { socket.onmessage = (event) => {
try { try {
const message = JSON.parse(event.data); const message = JSON.parse(event.data);
console.log('WebSocket message received:', message); console.log('WebSocket message received:', message, 'Current isRunning state:', isRunning);
switch (message.type) { switch (message.type) {
case 'output': case 'output':
@@ -217,10 +268,14 @@ int main() {
case 'status': case 'status':
let statusText = ''; let statusText = '';
let statusValue = '';
if (typeof message.content === 'object') { if (typeof message.content === 'object') {
statusText = `Status: ${message.content.status}`; statusText = `Status: ${message.content.status}`;
statusValue = message.content.status;
} else { } else {
statusText = `Status: ${message.content}`; statusText = `Status: ${message.content}`;
statusValue = message.content;
} }
setTerminalOutput(prev => [ setTerminalOutput(prev => [
@@ -229,11 +284,11 @@ int main() {
]); ]);
// If status contains "completed" or "failed", stop running // If status contains "completed" or "failed", stop running
if ((typeof message.content === 'string' && if (statusValue.includes('completed') || statusValue.includes('failed')) {
(message.content.includes('completed') || message.content.includes('failed'))) || console.log('Execution completed or failed, stopping');
(message.content.status && setTimeout(() => {
(message.content.status.includes('completed') || message.content.status.includes('failed')))) {
setIsRunning(false); setIsRunning(false);
}, 500); // Small delay to ensure UI updates properly
} }
break; break;
@@ -249,14 +304,29 @@ int main() {
...prev, ...prev,
{ type: 'error', content: errorContent } { type: 'error', content: errorContent }
]); ]);
console.log('Error received, enabling buttons');
setTimeout(() => {
setIsRunning(false); setIsRunning(false);
}, 500); // Small delay to ensure UI updates properly
break; break;
case 'system': case 'system':
const systemContent = String(message.content);
setTerminalOutput(prev => [ setTerminalOutput(prev => [
...prev, ...prev,
{ type: 'system', content: String(message.content) } { type: 'system', content: systemContent }
]); ]);
// Check for connection closing message which indicates execution is complete
if (systemContent.includes('Connection will close') ||
systemContent.includes('completed successfully') ||
systemContent.includes('Execution completed')) {
console.log('System message indicates completion, enabling buttons');
setTimeout(() => {
setIsRunning(false);
}, 500);
}
break; break;
default: default:
@@ -280,20 +350,57 @@ int main() {
...prev, ...prev,
{ type: 'error', content: 'WebSocket connection error' } { type: 'error', content: 'WebSocket connection error' }
]); ]);
console.log('WebSocket error, enabling buttons');
setTimeout(() => {
setIsRunning(false); setIsRunning(false);
}, 500); // Small delay to ensure UI updates properly
}; };
socket.onclose = () => { socket.onclose = () => {
console.log('WebSocket connection closed'); console.log('WebSocket connection closed');
setActiveSocket(null); setActiveSocket(null);
// Ensure buttons are re-enabled when the connection closes
setTimeout(() => {
resetExecutionState();
}, 100);
}; };
// Set the socket reference early to ensure we can clean it up if needed
socketRef.current = socket; socketRef.current = socket;
return socket; return socket;
}; };
// Handle code execution // Handle code execution
const runCode = async () => { const runCode = async () => {
console.log('Run button clicked, current state:', {
isRunning,
socketState: activeSocket ? activeSocket.readyState : 'no socket',
socketRefState: socketRef.current ? socketRef.current.readyState : 'no socket ref'
});
// First make sure previous connections are fully closed
resetExecutionState();
// Increase the delay to ensure clean state before starting new execution
setTimeout(async () => {
// Double-check socket state before proceeding
if (activeSocket || socketRef.current) {
console.warn('Socket still exists after reset, forcing cleanup');
if (activeSocket && activeSocket.readyState !== WebSocket.CLOSED) {
activeSocket.close();
}
if (socketRef.current && socketRef.current.readyState !== WebSocket.CLOSED) {
socketRef.current.close();
}
socketRef.current = null;
setActiveSocket(null);
// Extra delay to ensure socket is fully closed
await new Promise(resolve => setTimeout(resolve, 100));
}
setIsRunning(true); setIsRunning(true);
setTerminalOutput([ setTerminalOutput([
{ type: 'system', content: `Running ${problems[activeQuestion].id}...` } { type: 'system', content: `Running ${problems[activeQuestion].id}...` }
@@ -309,7 +416,7 @@ int main() {
body: JSON.stringify({ body: JSON.stringify({
code: code, code: code,
language: getLanguageIdentifier(language), language: getLanguageIdentifier(language),
input: '', // Add input if needed input: '',
}), }),
}); });
@@ -329,8 +436,10 @@ int main() {
...prev, ...prev,
{ type: 'error', content: `Error: ${error.message}` } { type: 'error', content: `Error: ${error.message}` }
]); ]);
setIsRunning(false);
resetExecutionState();
} }
}, 200); // Increased delay to ensure clean state
}; };
// Handle code submission // Handle code submission
@@ -394,6 +503,18 @@ int main() {
); );
}; };
// Add this useEffect to monitor socket state
useEffect(() => {
// If we have an active socket but aren't running, we should clean up
if (activeSocket && !isRunning) {
console.log('Cleaning up inactive socket');
if (activeSocket.readyState === WebSocket.OPEN) {
activeSocket.close();
}
setActiveSocket(null);
}
}, [activeSocket, isRunning]);
return ( return (
<div className="code-challenge-container"> <div className="code-challenge-container">
<header className="code-challenge-header"> <header className="code-challenge-header">
@@ -439,6 +560,7 @@ int main() {
onChange={(e) => setLanguage(e.target.value)} onChange={(e) => setLanguage(e.target.value)}
className="language-selector" className="language-selector"
> >
<option value="JavaScript">JavaScript</option>
<option value="Python">Python</option> <option value="Python">Python</option>
<option value="Java">Java</option> <option value="Java">Java</option>
<option value="C++">C++</option> <option value="C++">C++</option>
@@ -458,16 +580,34 @@ int main() {
className="run-btn" className="run-btn"
onClick={runCode} onClick={runCode}
disabled={isRunning} disabled={isRunning}
title={isRunning ? "Code execution in progress..." : "Run code"}
> >
{isRunning ? (
<>
<span className="loading-spinner"></span> Running...
</>
) : (
<>
<Play size={16} /> Run <Play size={16} /> Run
</>
)}
</button> </button>
<button <button
className="submit-btn" className="submit-btn"
onClick={submitCode} onClick={submitCode}
disabled={isRunning} disabled={isRunning}
title={isRunning ? "Code execution in progress..." : "Submit solution"}
> >
{isRunning ? (
<>
<span className="loading-spinner"></span> Submitting...
</>
) : (
<>
<Send size={16} /> Submit <Send size={16} /> Submit
</>
)}
</button> </button>
</div> </div>
</div> </div>
@@ -475,8 +615,8 @@ int main() {
<div className="editor-container"> <div className="editor-container">
<Editor <Editor
height="100%" height="100%"
defaultLanguage="javascript" defaultLanguage="python"
language={language.toLowerCase()} language={language.toLowerCase() === 'c++' ? 'cpp' : language.toLowerCase()}
value={code} value={code}
onChange={(value) => setCode(value)} onChange={(value) => setCode(value)}
theme="vs-dark" theme="vs-dark"
@@ -516,10 +656,25 @@ int main() {
className="terminal-input" className="terminal-input"
placeholder="Type here..." placeholder="Type here..."
disabled={!isRunning} disabled={!isRunning}
onKeyPress={(e) => { // Update the ref callback
if (e.key === 'Enter' && activeSocket) { ref={(inputEl) => {
const input = e.target.value; // Auto-focus input when isRunning changes to true
// Send input to WebSocket with the correct format if (inputEl && isRunning) {
inputEl.focus();
// Clear any previous input
inputEl.value = '';
}
}}
onKeyDown={(e) => { // Change from onKeyPress to onKeyDown for better cross-browser support
if (e.key === 'Enter') {
e.preventDefault(); // Prevent default to avoid form submissions
const input = e.target.value.trim();
if (!input) return; // Skip empty input
if (activeSocket && activeSocket.readyState === WebSocket.OPEN) {
try {
// Send input to server
activeSocket.send(JSON.stringify({ activeSocket.send(JSON.stringify({
"type": "input", "type": "input",
"content": input "content": input
@@ -533,6 +688,60 @@ int main() {
// Clear the input field // Clear the input field
e.target.value = ''; e.target.value = '';
} catch (error) {
console.error("Error sending input:", error);
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Failed to send input: ${error.message}` }
]);
}
} else {
// Better error message with socket state information
const socketState = activeSocket ?
['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'][activeSocket.readyState] :
'NO_SOCKET';
console.log(`Cannot send input: Socket state is ${socketState}`);
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Cannot send input: connection not available (${socketState})` }
]);
}
}
}}
onKeyPress={(e) => {
if (e.key === 'Enter' && activeSocket && activeSocket.readyState === WebSocket.OPEN) {
const input = e.target.value;
// Send input to WebSocket with the correct format
try {
activeSocket.send(JSON.stringify({
"type": "input",
"content": input
}));
// Add input to terminal output
setTerminalOutput(prev => [
...prev,
{ type: 'system', content: `$ ${input}` }
]);
// Clear the input field
e.target.value = '';
} catch (error) {
console.error("Error sending input:", error);
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Failed to send input: ${error.message}` }
]);
}
} else if (e.key === 'Enter') {
// Inform user if socket isn't available
if (!activeSocket || activeSocket.readyState !== WebSocket.OPEN) {
setTerminalOutput(prev => [
...prev,
{ type: 'error', content: `Cannot send input: connection closed` }
]);
}
} }
}} }}
/> />