add terminal input handling and styling to CodeChallenge component

This commit is contained in:
2025-11-03 15:30:58 +05:30
parent 2508731ec7
commit 356d532beb
3 changed files with 227 additions and 12 deletions

View File

@@ -16,7 +16,10 @@ const CodeChallenge = () => {
const [activeSocket, setActiveSocket] = useState(null); const [activeSocket, setActiveSocket] = useState(null);
const [submissionId, setSubmissionId] = useState(null); const [submissionId, setSubmissionId] = useState(null);
const [timeRemaining, setTimeRemaining] = useState(null); const [timeRemaining, setTimeRemaining] = useState(null);
const [terminalInput, setTerminalInput] = useState('');
const [waitingForInput, setWaitingForInput] = useState(false);
const socketRef = useRef(null); const socketRef = useRef(null);
const terminalInputRef = useRef(null);
const { token } = useAuth(); const { token } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -26,6 +29,8 @@ const CodeChallenge = () => {
if (testData) { if (testData) {
try { try {
const parsedData = JSON.parse(testData); const parsedData = JSON.parse(testData);
console.log('Loaded test data:', parsedData);
console.log('End time in test data:', parsedData.end_time);
setTest(parsedData); setTest(parsedData);
if (parsedData.questions && parsedData.questions.length > 0) { if (parsedData.questions && parsedData.questions.length > 0) {
setQuestions(parsedData.questions); setQuestions(parsedData.questions);
@@ -45,11 +50,36 @@ const CodeChallenge = () => {
// Timer countdown // Timer countdown
useEffect(() => { 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 updateTimer = () => {
const now = new Date(); 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; const diff = endTime - now;
if (diff <= 0) { if (diff <= 0) {
@@ -111,6 +141,8 @@ const CodeChallenge = () => {
// Reset execution state to allow rerunning // Reset execution state to allow rerunning
const resetExecutionState = () => { const resetExecutionState = () => {
setIsRunning(false); setIsRunning(false);
setWaitingForInput(false);
setTerminalInput('');
// Properly close the socket if it exists and is open // Properly close the socket if it exists and is open
if (socketRef.current) { if (socketRef.current) {
@@ -126,6 +158,32 @@ const CodeChallenge = () => {
console.log('Execution state reset, buttons should be enabled'); 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 // Example problem data
const problems = { const problems = {
"Q.1": { "Q.1": {
@@ -348,6 +406,13 @@ int main() {
socket.onopen = () => { socket.onopen = () => {
console.log('WebSocket connection established'); console.log('WebSocket connection established');
setActiveSocket(socket); setActiveSocket(socket);
setWaitingForInput(true);
// Focus the input field when connection is ready
setTimeout(() => {
if (terminalInputRef.current) {
terminalInputRef.current.focus();
}
}, 100);
}; };
socket.onmessage = (event) => { socket.onmessage = (event) => {
@@ -369,10 +434,18 @@ int main() {
case 'input_prompt': case 'input_prompt':
// Handle input prompt message (e.g., "Enter your name:") // Handle input prompt message (e.g., "Enter your name:")
console.log('Input prompt received');
setTerminalOutput(prev => [ setTerminalOutput(prev => [
...prev, ...prev,
{ type: 'output', content: message.content } { type: 'output', content: message.content }
]); ]);
// Ensure input is enabled and focused
setWaitingForInput(true);
setTimeout(() => {
if (terminalInputRef.current) {
terminalInputRef.current.focus();
}
}, 50);
break; break;
case 'status': case 'status':
@@ -434,6 +507,7 @@ int main() {
console.log('System message indicates completion, enabling buttons'); console.log('System message indicates completion, enabling buttons');
setTimeout(() => { setTimeout(() => {
setIsRunning(false); setIsRunning(false);
setWaitingForInput(false);
}, 500); }, 500);
} }
break; break;
@@ -461,6 +535,7 @@ int main() {
]); ]);
console.log('WebSocket error, enabling buttons'); console.log('WebSocket error, enabling buttons');
setWaitingForInput(false);
setTimeout(() => { setTimeout(() => {
setIsRunning(false); setIsRunning(false);
}, 500); // Small delay to ensure UI updates properly }, 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 // Handle final test submission
const handleSubmitTest = async () => { const handleSubmitTest = async () => {
if (!test) { if (!test) {
@@ -933,20 +1069,45 @@ int main() {
</button> </button>
</div> </div>
</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 ? ( {terminalOutput.length === 0 ? (
<div className="console-placeholder"> <div className="console-placeholder">
Console output will appear here... Console output will appear here...
</div> </div>
) : ( ) : (
terminalOutput.map((line, index) => ( <>
<div {terminalOutput.map((line, index) => (
key={index} <div
className={`console-line ${line.type}`} key={index}
> className={`console-line ${line.type}`}
{line.content} >
</div> {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>
</div> </div>
@@ -971,7 +1132,10 @@ int main() {
</> </>
)} )}
</button> </button>
<button className="action-bar-btn action-save"> <button
className="action-bar-btn action-save"
onClick={handleSaveAndNext}
>
Save & Next Save & Next
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M5 12h14"/> <path d="M5 12h14"/>

View File

@@ -68,6 +68,11 @@ const TestList = () => {
if (data.success) { if (data.success) {
localStorage.setItem('currentTest', JSON.stringify({ localStorage.setItem('currentTest', JSON.stringify({
id: test.id, id: test.id,
title: test.title,
description: test.description,
duration_minutes: test.duration_minutes,
start_time: test.start_time,
end_time: test.end_time,
questions: data.questions, questions: data.questions,
currentQuestionIndex: 0 currentQuestionIndex: 0
})); }));

View File

@@ -1686,6 +1686,52 @@ body {
color: #212529; color: #212529;
} }
.console-line.input {
color: #28a745;
font-weight: 500;
}
/* Console Input Line - makes it look like terminal output */
.console-line.console-input-line {
display: flex;
align-items: center;
margin-bottom: 0;
}
.console-cursor {
color: #6c757d;
margin-right: 4px;
}
.console-input-form {
display: flex;
align-items: center;
width: 100%;
margin: 0;
}
.console-input {
flex: 1;
background: transparent;
border: none;
outline: none;
color: #212529;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 12px;
padding: 0;
line-height: 1.5;
min-width: 200px;
}
.console-input::placeholder {
color: transparent;
}
/* Add blinking cursor effect */
.console-input:focus {
caret-color: #212529;
}
/* Action Bar */ /* Action Bar */
.action-bar { .action-bar {
position: absolute; position: absolute;