add terminal input handling and styling to CodeChallenge component
This commit is contained in:
@@ -16,7 +16,10 @@ const CodeChallenge = () => {
|
||||
const [activeSocket, setActiveSocket] = useState(null);
|
||||
const [submissionId, setSubmissionId] = useState(null);
|
||||
const [timeRemaining, setTimeRemaining] = useState(null);
|
||||
const [terminalInput, setTerminalInput] = useState('');
|
||||
const [waitingForInput, setWaitingForInput] = useState(false);
|
||||
const socketRef = useRef(null);
|
||||
const terminalInputRef = useRef(null);
|
||||
const { token } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -26,6 +29,8 @@ const CodeChallenge = () => {
|
||||
if (testData) {
|
||||
try {
|
||||
const parsedData = JSON.parse(testData);
|
||||
console.log('Loaded test data:', parsedData);
|
||||
console.log('End time in test data:', parsedData.end_time);
|
||||
setTest(parsedData);
|
||||
if (parsedData.questions && parsedData.questions.length > 0) {
|
||||
setQuestions(parsedData.questions);
|
||||
@@ -45,11 +50,36 @@ const CodeChallenge = () => {
|
||||
|
||||
// Timer countdown
|
||||
useEffect(() => {
|
||||
if (!test || !test.end_time) return;
|
||||
if (!test) return;
|
||||
|
||||
// Check for different possible time field names
|
||||
const endTimeValue = test.end_time || test.endTime || test.end_date;
|
||||
|
||||
if (!endTimeValue) {
|
||||
console.warn('No end time found in test data:', test);
|
||||
return;
|
||||
}
|
||||
|
||||
const updateTimer = () => {
|
||||
const now = new Date();
|
||||
const endTime = new Date(test.end_time);
|
||||
let endTime;
|
||||
|
||||
// Try to parse the end time - handle different formats
|
||||
try {
|
||||
endTime = new Date(endTimeValue);
|
||||
|
||||
// Check if the date is valid
|
||||
if (isNaN(endTime.getTime())) {
|
||||
console.error('Invalid date format:', endTimeValue);
|
||||
setTimeRemaining('Invalid Date');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing end time:', error);
|
||||
setTimeRemaining('Invalid Date');
|
||||
return;
|
||||
}
|
||||
|
||||
const diff = endTime - now;
|
||||
|
||||
if (diff <= 0) {
|
||||
@@ -111,6 +141,8 @@ const CodeChallenge = () => {
|
||||
// Reset execution state to allow rerunning
|
||||
const resetExecutionState = () => {
|
||||
setIsRunning(false);
|
||||
setWaitingForInput(false);
|
||||
setTerminalInput('');
|
||||
|
||||
// Properly close the socket if it exists and is open
|
||||
if (socketRef.current) {
|
||||
@@ -126,6 +158,32 @@ const CodeChallenge = () => {
|
||||
console.log('Execution state reset, buttons should be enabled');
|
||||
};
|
||||
|
||||
// Handle terminal input submission
|
||||
const handleTerminalInput = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!terminalInput.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display the input in terminal
|
||||
setTerminalOutput(prev => [
|
||||
...prev,
|
||||
{ type: 'input', content: terminalInput }
|
||||
]);
|
||||
|
||||
// If socket is available, send input to backend
|
||||
if (activeSocket && activeSocket.readyState === WebSocket.OPEN) {
|
||||
activeSocket.send(JSON.stringify({
|
||||
type: 'input',
|
||||
content: terminalInput + '\n'
|
||||
}));
|
||||
}
|
||||
|
||||
// Clear input
|
||||
setTerminalInput('');
|
||||
};
|
||||
|
||||
// Example problem data
|
||||
const problems = {
|
||||
"Q.1": {
|
||||
@@ -348,6 +406,13 @@ int main() {
|
||||
socket.onopen = () => {
|
||||
console.log('WebSocket connection established');
|
||||
setActiveSocket(socket);
|
||||
setWaitingForInput(true);
|
||||
// Focus the input field when connection is ready
|
||||
setTimeout(() => {
|
||||
if (terminalInputRef.current) {
|
||||
terminalInputRef.current.focus();
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
@@ -369,10 +434,18 @@ int main() {
|
||||
|
||||
case 'input_prompt':
|
||||
// Handle input prompt message (e.g., "Enter your name:")
|
||||
console.log('Input prompt received');
|
||||
setTerminalOutput(prev => [
|
||||
...prev,
|
||||
{ type: 'output', content: message.content }
|
||||
]);
|
||||
// Ensure input is enabled and focused
|
||||
setWaitingForInput(true);
|
||||
setTimeout(() => {
|
||||
if (terminalInputRef.current) {
|
||||
terminalInputRef.current.focus();
|
||||
}
|
||||
}, 50);
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
@@ -434,6 +507,7 @@ int main() {
|
||||
console.log('System message indicates completion, enabling buttons');
|
||||
setTimeout(() => {
|
||||
setIsRunning(false);
|
||||
setWaitingForInput(false);
|
||||
}, 500);
|
||||
}
|
||||
break;
|
||||
@@ -461,6 +535,7 @@ int main() {
|
||||
]);
|
||||
|
||||
console.log('WebSocket error, enabling buttons');
|
||||
setWaitingForInput(false);
|
||||
setTimeout(() => {
|
||||
setIsRunning(false);
|
||||
}, 500); // Small delay to ensure UI updates properly
|
||||
@@ -622,6 +697,67 @@ int main() {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle save and move to next question
|
||||
const handleSaveAndNext = async () => {
|
||||
const currentQuestion = getCurrentQuestion();
|
||||
|
||||
if (!currentQuestion || !test) {
|
||||
alert('No test data available.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save current answer
|
||||
try {
|
||||
const apiUrl = import.meta.env.VITE_FACULTY_API_URL || 'http://localhost:5000/api';
|
||||
const response = await fetch(`${apiUrl}/students/submissions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
testId: test.id,
|
||||
answers: [{
|
||||
questionId: currentQuestion.id,
|
||||
submittedAnswer: code
|
||||
}]
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.message || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// Store submission locally
|
||||
localStorage.setItem(`submission_${test.id}_${currentQuestion.id}`, JSON.stringify({
|
||||
code,
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
|
||||
setTerminalOutput([
|
||||
{ type: 'system', content: `✓ Answer saved for Question ${getQuestionIndex(activeQuestion) + 1}` }
|
||||
]);
|
||||
|
||||
// Move to next question if available
|
||||
const currentIndex = getQuestionIndex(activeQuestion);
|
||||
if (currentIndex < questions.length - 1) {
|
||||
const nextQuestionKey = `Q.${currentIndex + 2}`;
|
||||
setActiveQuestion(nextQuestionKey);
|
||||
} else {
|
||||
// If last question, show message
|
||||
setTerminalOutput([
|
||||
{ type: 'system', content: '✓ Answer saved!' },
|
||||
{ type: 'output', content: 'This is the last question. Click "Submit Test" to finish.' }
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error saving answer:', error);
|
||||
alert(`Error saving answer: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle final test submission
|
||||
const handleSubmitTest = async () => {
|
||||
if (!test) {
|
||||
@@ -933,20 +1069,45 @@ int main() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="console-content">
|
||||
<div
|
||||
className="console-content"
|
||||
onClick={() => {
|
||||
if (waitingForInput && terminalInputRef.current) {
|
||||
terminalInputRef.current.focus();
|
||||
}
|
||||
}}
|
||||
style={{ cursor: waitingForInput ? 'text' : 'default' }}
|
||||
>
|
||||
{terminalOutput.length === 0 ? (
|
||||
<div className="console-placeholder">
|
||||
Console output will appear here...
|
||||
</div>
|
||||
) : (
|
||||
terminalOutput.map((line, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`console-line ${line.type}`}
|
||||
>
|
||||
{line.content}
|
||||
</div>
|
||||
))
|
||||
<>
|
||||
{terminalOutput.map((line, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`console-line ${line.type}`}
|
||||
>
|
||||
{line.content}
|
||||
</div>
|
||||
))}
|
||||
{waitingForInput && (
|
||||
<div className="console-line console-input-line">
|
||||
<form onSubmit={handleTerminalInput} className="console-input-form">
|
||||
<span className="console-cursor">{'> '}</span>
|
||||
<input
|
||||
ref={terminalInputRef}
|
||||
type="text"
|
||||
value={terminalInput}
|
||||
onChange={(e) => setTerminalInput(e.target.value)}
|
||||
className="console-input"
|
||||
autoFocus
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -971,7 +1132,10 @@ int main() {
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button className="action-bar-btn action-save">
|
||||
<button
|
||||
className="action-bar-btn action-save"
|
||||
onClick={handleSaveAndNext}
|
||||
>
|
||||
Save & Next
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M5 12h14"/>
|
||||
|
||||
Reference in New Issue
Block a user