Enhance CodeChallenge component with improved styling for problem examples and refined WebSocket handling for execution state management
This commit is contained in:
@@ -115,7 +115,21 @@
|
||||
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 {
|
||||
flex: 1;
|
||||
|
||||
@@ -16,14 +16,34 @@ const CodeChallenge = () => {
|
||||
// Map frontend language names to backend language identifiers
|
||||
const getLanguageIdentifier = (uiLanguage) => {
|
||||
const languageMap = {
|
||||
'javascript': 'javascript',
|
||||
'python': 'python',
|
||||
'java': 'java',
|
||||
'c++': 'cpp',
|
||||
'c': 'c'
|
||||
};
|
||||
// Important: make sure we convert to lowercase to match the backend's expected format
|
||||
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
|
||||
const problems = {
|
||||
"Q.1": {
|
||||
@@ -112,13 +132,9 @@ var isValid = function(s) {
|
||||
|
||||
// Get appropriate starter code based on language
|
||||
const getStarterCode = (problem, lang) => {
|
||||
// Default JavaScript starter code is in the problem object
|
||||
if (lang === 'JavaScript') {
|
||||
return problem.starterCode;
|
||||
}
|
||||
|
||||
// Language-specific starter code templates
|
||||
const templates = {
|
||||
'JavaScript': problem.starterCode,
|
||||
'C': `#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
@@ -133,6 +149,8 @@ int main() {
|
||||
'Python': `# ${problem.title}
|
||||
def solution():
|
||||
# Write your solution here
|
||||
# Use input() for user input in Python
|
||||
# Example: name = input("Enter your name: ")
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -175,13 +193,46 @@ int main() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Connect to WebSocket
|
||||
const connectToWebSocket = (id) => {
|
||||
// Close existing connection if any
|
||||
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
|
||||
socketRef.current.close();
|
||||
// Set a safety timeout to ensure buttons are re-enabled if execution hangs
|
||||
useEffect(() => {
|
||||
let safetyTimer = null;
|
||||
|
||||
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 socket = new WebSocket(wsUrl);
|
||||
|
||||
@@ -193,7 +244,7 @@ int main() {
|
||||
socket.onmessage = (event) => {
|
||||
try {
|
||||
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) {
|
||||
case 'output':
|
||||
@@ -217,10 +268,14 @@ int main() {
|
||||
|
||||
case 'status':
|
||||
let statusText = '';
|
||||
let statusValue = '';
|
||||
|
||||
if (typeof message.content === 'object') {
|
||||
statusText = `Status: ${message.content.status}`;
|
||||
statusValue = message.content.status;
|
||||
} else {
|
||||
statusText = `Status: ${message.content}`;
|
||||
statusValue = message.content;
|
||||
}
|
||||
|
||||
setTerminalOutput(prev => [
|
||||
@@ -229,11 +284,11 @@ int main() {
|
||||
]);
|
||||
|
||||
// If status contains "completed" or "failed", stop running
|
||||
if ((typeof message.content === 'string' &&
|
||||
(message.content.includes('completed') || message.content.includes('failed'))) ||
|
||||
(message.content.status &&
|
||||
(message.content.status.includes('completed') || message.content.status.includes('failed')))) {
|
||||
if (statusValue.includes('completed') || statusValue.includes('failed')) {
|
||||
console.log('Execution completed or failed, stopping');
|
||||
setTimeout(() => {
|
||||
setIsRunning(false);
|
||||
}, 500); // Small delay to ensure UI updates properly
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -249,14 +304,29 @@ int main() {
|
||||
...prev,
|
||||
{ type: 'error', content: errorContent }
|
||||
]);
|
||||
|
||||
console.log('Error received, enabling buttons');
|
||||
setTimeout(() => {
|
||||
setIsRunning(false);
|
||||
}, 500); // Small delay to ensure UI updates properly
|
||||
break;
|
||||
|
||||
case 'system':
|
||||
const systemContent = String(message.content);
|
||||
setTerminalOutput(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;
|
||||
|
||||
default:
|
||||
@@ -280,20 +350,57 @@ int main() {
|
||||
...prev,
|
||||
{ type: 'error', content: 'WebSocket connection error' }
|
||||
]);
|
||||
|
||||
console.log('WebSocket error, enabling buttons');
|
||||
setTimeout(() => {
|
||||
setIsRunning(false);
|
||||
}, 500); // Small delay to ensure UI updates properly
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
console.log('WebSocket connection closed');
|
||||
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;
|
||||
return socket;
|
||||
};
|
||||
|
||||
// Handle code execution
|
||||
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);
|
||||
setTerminalOutput([
|
||||
{ type: 'system', content: `Running ${problems[activeQuestion].id}...` }
|
||||
@@ -309,7 +416,7 @@ int main() {
|
||||
body: JSON.stringify({
|
||||
code: code,
|
||||
language: getLanguageIdentifier(language),
|
||||
input: '', // Add input if needed
|
||||
input: '',
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -329,8 +436,10 @@ int main() {
|
||||
...prev,
|
||||
{ type: 'error', content: `Error: ${error.message}` }
|
||||
]);
|
||||
setIsRunning(false);
|
||||
|
||||
resetExecutionState();
|
||||
}
|
||||
}, 200); // Increased delay to ensure clean state
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<div className="code-challenge-container">
|
||||
<header className="code-challenge-header">
|
||||
@@ -439,6 +560,7 @@ int main() {
|
||||
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>
|
||||
@@ -458,16 +580,34 @@ int main() {
|
||||
className="run-btn"
|
||||
onClick={runCode}
|
||||
disabled={isRunning}
|
||||
title={isRunning ? "Code execution in progress..." : "Run code"}
|
||||
>
|
||||
{isRunning ? (
|
||||
<>
|
||||
<span className="loading-spinner"></span> Running...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Play size={16} /> Run
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="submit-btn"
|
||||
onClick={submitCode}
|
||||
disabled={isRunning}
|
||||
title={isRunning ? "Code execution in progress..." : "Submit solution"}
|
||||
>
|
||||
{isRunning ? (
|
||||
<>
|
||||
<span className="loading-spinner"></span> Submitting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send size={16} /> Submit
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -475,8 +615,8 @@ int main() {
|
||||
<div className="editor-container">
|
||||
<Editor
|
||||
height="100%"
|
||||
defaultLanguage="javascript"
|
||||
language={language.toLowerCase()}
|
||||
defaultLanguage="python"
|
||||
language={language.toLowerCase() === 'c++' ? 'cpp' : language.toLowerCase()}
|
||||
value={code}
|
||||
onChange={(value) => setCode(value)}
|
||||
theme="vs-dark"
|
||||
@@ -516,10 +656,25 @@ int main() {
|
||||
className="terminal-input"
|
||||
placeholder="Type here..."
|
||||
disabled={!isRunning}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter' && activeSocket) {
|
||||
const input = e.target.value;
|
||||
// Send input to WebSocket with the correct format
|
||||
// Update the ref callback
|
||||
ref={(inputEl) => {
|
||||
// Auto-focus input when isRunning changes to true
|
||||
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({
|
||||
"type": "input",
|
||||
"content": input
|
||||
@@ -533,6 +688,60 @@ int main() {
|
||||
|
||||
// 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 {
|
||||
// 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` }
|
||||
]);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user