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;
|
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;
|
||||||
|
|||||||
@@ -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` }
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user