elemes/templates/lesson.html

628 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ lesson_title }} - C Programming Learning System</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
<link rel="stylesheet" href="{{ url_for('send_static', path='style.css') }}">
<style>
.hljs {
background: #f8f9fa;
padding: 12px;
border-radius: 4px;
overflow-x: auto;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-code"></i> C Programming Learning System
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/"><i class="fas fa-home"></i> Home</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
<div class="d-flex" id="token-section">
<form class="d-flex" id="token-form" style="display: flex; align-items: center;">
<input class="form-control me-2" type="text" id="student-token" placeholder="Enter token" style="width: 200px;">
<button class="btn btn-outline-light" type="submit">Login</button>
</form>
<div id="student-info" class="text-light" style="margin-left: 15px; display: none;"></div>
</div>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<div class="row">
<div class="col-md-8">
<div class="lesson-content">
{{ lesson_content | safe }}
</div>
<div class="exercise-section mt-5">
<h3><i class="fas fa-laptop-code"></i> Exercise</h3>
{% if exercise_content %}
<div class="exercise-content">
{{ exercise_content | safe }}
</div>
{% else %}
<div class="exercise-content bg-light p-3 rounded">
<p class="mb-0"><em>No specific exercise for this lesson, but you can practice writing C code below.</em></p>
</div>
{% endif %}
<div class="editor-section mt-4">
<h4><i class="fas fa-code"></i> Code Editor</h4>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center" style="background-color: #4a76a8; color: white;">
<span><i class="fas fa-file-code me-2"></i>C code.c</span>
<div>
<button id="theme-toggle" class="btn btn-sm btn-outline-light me-2">
<i class="fas fa-moon"></i> Dark
</button>
<button id="solution-code" class="btn btn-sm btn-outline-light me-2 d-none">
<i class="fas fa-lightbulb"></i> Show Solution
</button>
<button id="run-code" class="btn btn-sm btn-success me-2">
<i class="fas fa-play"></i> Run
</button>
<button id="reset-code" class="btn btn-sm btn-outline-light">
<i class="fas fa-redo"></i> Reset
</button>
</div>
</div>
<div class="card-body p-0">
<div class="code-editor-container">
<div id="editor-wrapper" class="editor-wrapper" style="position: relative; height: 300px; display: flex;">
<div id="line-numbers" class="line-numbers" style="width: 50px; text-align: right; padding: 10px 5px; overflow: hidden; user-select: none; flex-shrink: 0;">
1
</div>
<div style="flex: 1; position: relative;">
<textarea id="code-editor"
style="height: 100%; width: 100%; padding: 10px; font-family: monospace; resize: none; border: none; outline: none; tab-size: 4;"
placeholder="Type your C code here..."></textarea>
</div>
</div>
</div>
</div>
</div>
<!-- Success message card -->
<div id="success-message" class="alert alert-success d-none mt-3" role="alert">
<i class="fas fa-check-circle me-2"></i>
<strong>Tugas anda berhasil!</strong> Output sesuai dengan yang diharapkan.
</div>
<div id="output" class="mt-3 p-3 border rounded bg-light d-none" style="max-height: 200px; overflow-y: auto;">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="mb-0"><i class="fas fa-terminal"></i> Output</h5>
<button id="clear-output" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-eraser"></i> Clear
</button>
</div>
<pre id="output-content" class="mb-0"></pre>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-info-circle"></i> Lesson Information</h5>
</div>
<div class="card-body">
<p><strong>Current Lesson:</strong> {{ lesson_title }}</p>
{% if lesson_info %}
<div class="lesson-info-content">
{{ lesson_info | safe }}
</div>
{% else %}
<p class="text-muted">Complete the exercise to practice what you've learned.</p>
{% endif %}
</div>
</div>
<div class="card mt-4">
<div class="card-header">
<h5><i class="fas fa-list"></i> All Lessons</h5>
</div>
<div class="list-group list-group-flush">
{% for lesson in get_lessons() %}
<a href="{{ url_for('lesson', filename=lesson.filename) }}"
class="list-group-item list-group-item-action {% if lesson.filename == request.view_args.filename %}active{% endif %}">
{{ lesson.title }}
</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<footer class="footer mt-5 py-4 bg-light">
<div class="container text-center">
<span class="text-muted">C Programming Learning System &copy; 2025</span>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get initial code from the template
const initialCode = {{ initial_code | tojson }};
const solutionCode = {{ solution_code | tojson }};
const expectedOutput = {{ expected_output | tojson }};
// Get DOM elements
const runButton = document.getElementById('run-code');
const resetButton = document.getElementById('reset-code');
const clearOutputButton = document.getElementById('clear-output');
const outputDiv = document.getElementById('output');
const outputContent = document.getElementById('output-content');
const solutionButton = document.getElementById('solution-code');
const themeToggle = document.getElementById('theme-toggle');
const codeEditor = document.getElementById('code-editor');
// Set initial code
codeEditor.value = initialCode;
// Check for saved theme preference
const savedTheme = localStorage.getItem('editor-theme');
let isDarkTheme = savedTheme === 'dark';
// Get the line numbers container (now part of HTML)
const lineNumbersContainer = document.getElementById('line-numbers');
// Function to apply dark theme
function applyDarkTheme() {
codeEditor.classList.add('code-editor-dark');
outputDiv.classList.add('output-dark');
// Update line numbers for dark theme
lineNumbersContainer.style.backgroundColor = '#3e3d32';
lineNumbersContainer.style.color = '#75715e';
lineNumbersContainer.style.borderColor = '#49483e';
codeEditor.style.backgroundColor = '#272822';
codeEditor.style.color = '#f8f8f2';
}
// Function to apply light theme
function applyLightTheme() {
codeEditor.classList.remove('code-editor-dark');
outputDiv.classList.remove('output-dark');
// Update line numbers for light theme
lineNumbersContainer.style.backgroundColor = '#eef0f3';
lineNumbersContainer.style.color = '#6c757d';
lineNumbersContainer.style.borderColor = '#dee2e6';
codeEditor.style.backgroundColor = 'white';
codeEditor.style.color = 'black';
}
// Apply theme based on preference
if (isDarkTheme) {
applyDarkTheme();
themeToggle.innerHTML = '<i class="fas fa-sun"></i> Light';
} else {
applyLightTheme();
themeToggle.innerHTML = '<i class="fas fa-moon"></i> Dark';
}
// Function to update line numbers
function updateLineNumbers() {
const lines = codeEditor.value.split('\n');
const lineCount = lines.length || 1; // Ensure at least 1 line
let lineNumbersHTML = '';
for (let i = 1; i <= lineCount; i++) {
lineNumbersHTML += i + '<br>';
}
lineNumbersContainer.innerHTML = lineNumbersHTML;
}
// Initialize line numbers
updateLineNumbers();
// Update line numbers when content changes
codeEditor.addEventListener('input', updateLineNumbers);
codeEditor.addEventListener('scroll', function() {
lineNumbersContainer.scrollTop = codeEditor.scrollTop;
});
// Update line numbers on other events that might change content
codeEditor.addEventListener('keyup', updateLineNumbers);
codeEditor.addEventListener('paste', function() {
setTimeout(updateLineNumbers, 0);
});
// Prevent copy-paste in the code editor with multiple methods
codeEditor.addEventListener('paste', function(e) {
e.preventDefault();
e.stopPropagation();
alert('Copy-paste is not allowed in the code editor. Please type your code manually.');
return false;
});
// Additional paste prevention using onpaste attribute
codeEditor.onpaste = function(e) {
e.preventDefault();
e.stopPropagation();
alert('Copy-paste is not allowed in the code editor. Please type your code manually.');
return false;
};
codeEditor.addEventListener('copy', function(e) {
e.preventDefault();
e.stopPropagation();
alert('Copy is not allowed in the code editor. Please type your code manually.');
});
codeEditor.addEventListener('cut', function(e) {
e.preventDefault();
e.stopPropagation();
alert('Cut is not allowed in the code editor. Please type your code manually.');
});
// Prevent right-click context menu
codeEditor.addEventListener('contextmenu', function(e) {
e.preventDefault();
e.stopPropagation();
alert('Copy-paste is not allowed in the code editor. Please type your code manually.');
});
// Add keyboard shortcut for running code (Ctrl+Enter)
codeEditor.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.key === 'Enter') {
runButton.click();
}
// Handle Tab key to insert 4 spaces instead of changing focus
if (e.key === 'Tab') {
e.preventDefault();
const start = this.selectionStart;
const end = this.selectionEnd;
// Insert 4 spaces at cursor position
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
// Move cursor to after the inserted spaces
this.selectionStart = this.selectionEnd = start + 4;
// Update line numbers after tab
setTimeout(updateLineNumbers, 1);
}
});
// Run code functionality
runButton.addEventListener('click', function() {
const code = codeEditor.value;
if (!code.trim()) {
alert('Please enter some C code to run.');
return;
}
// Show loading state
runButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Running...';
runButton.disabled = true;
// Create form data to send to the server
const formData = new FormData();
formData.append('code', code);
fetch('/compile', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
outputDiv.classList.remove('d-none');
if (data.success) {
outputContent.textContent = data.output || 'Program executed successfully with no output.';
outputContent.className = 'output-success';
// Check if output matches expected output (if available)
if (expectedOutput && expectedOutput !== "None" && expectedOutput !== "") {
const actualOutput = data.output || '';
if (actualOutput.trim() === expectedOutput.trim()) {
// Show success message card if element exists
const successElement = document.getElementById('success-message');
if (successElement) {
// Remove d-none to show the element
successElement.classList.remove('d-none');
// Hide the success message after 10 seconds
setTimeout(function() {
successElement.classList.add('d-none');
}, 10000);
}
// Show solution button if solution code exists
if (solutionButton && solutionCode && solutionCode !== "None" && solutionCode !== "") {
solutionButton.classList.remove('d-none');
}
// Track progress if student is logged in
const savedToken = localStorage.getItem('student_token');
if (savedToken) {
// Extract lesson name from the URL
const pathParts = window.location.pathname.split('/');
const lessonFilename = pathParts[pathParts.length - 1];
const lessonName = lessonFilename.replace('.md', '');
// Send progress to server
fetch('/track-progress', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
token: savedToken,
lesson_name: lessonName,
status: 'completed'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('Progress tracked successfully');
} else {
console.error('Failed to track progress:', data.message);
}
})
.catch(error => {
console.error('Error tracking progress:', error);
});
}
} else {
// Hide success message if output doesn't match
const successElement = document.getElementById('success-message');
if (successElement) {
successElement.classList.add('d-none');
}
// Hide solution button if output doesn't match
if (solutionButton) {
solutionButton.classList.add('d-none');
}
}
} else {
// Hide success message if no expected output
const successElement = document.getElementById('success-message');
if (successElement) {
successElement.classList.add('d-none');
}
// Hide solution button if no expected output
if (solutionButton) {
solutionButton.classList.add('d-none');
}
}
} else {
outputContent.textContent = data.error || data.output || 'An error occurred.';
outputContent.className = 'output-error';
// Hide success message if there's an error
const successElement = document.getElementById('success-message');
if (successElement) {
successElement.classList.add('d-none');
}
}
})
.catch(error => {
outputDiv.classList.remove('d-none');
outputContent.textContent = 'An error occurred while running the code: ' + error.message;
outputContent.className = 'output-error';
// Hide success message if there's an error
const successElement = document.getElementById('success-message');
if (successElement) {
successElement.classList.add('d-none');
}
})
.finally(() => {
// Reset button state
runButton.innerHTML = '<i class="fas fa-play"></i> Run';
runButton.disabled = false;
});
});
// Reset code functionality
resetButton.addEventListener('click', function() {
codeEditor.value = initialCode;
updateLineNumbers();
});
// Clear output functionality
clearOutputButton.addEventListener('click', function() {
outputDiv.classList.add('d-none');
});
// Add solution button functionality
if (solutionButton && solutionCode && solutionCode !== "None" && solutionCode !== "") {
solutionButton.addEventListener('click', function() {
codeEditor.value = solutionCode;
updateLineNumbers();
});
}
// Function to apply dark theme
function applyDarkTheme() {
codeEditor.classList.add('code-editor-dark');
outputDiv.classList.add('output-dark');
// Update line numbers for dark theme
lineNumbersContainer.style.backgroundColor = '#3e3d32';
lineNumbersContainer.style.color = '#75715e';
codeEditor.style.backgroundColor = '#272822';
codeEditor.style.color = '#f8f8f2';
}
// Function to apply light theme
function applyLightTheme() {
codeEditor.classList.remove('code-editor-dark');
outputDiv.classList.remove('output-dark');
// Update line numbers for light theme
lineNumbersContainer.style.backgroundColor = '#eef0f3';
lineNumbersContainer.style.color = '#6c757d';
codeEditor.style.backgroundColor = 'white';
codeEditor.style.color = 'black';
}
// Theme toggle functionality
themeToggle.addEventListener('click', function() {
isDarkTheme = !isDarkTheme;
if (isDarkTheme) {
applyDarkTheme();
themeToggle.innerHTML = '<i class="fas fa-sun"></i> Light';
localStorage.setItem('editor-theme', 'dark');
} else {
applyLightTheme();
themeToggle.innerHTML = '<i class="fas fa-moon"></i> Dark';
localStorage.setItem('editor-theme', 'light');
}
});
// Token login functionality
const tokenForm = document.getElementById('token-form');
const studentTokenInput = document.getElementById('student-token');
const studentInfoDiv = document.getElementById('student-info');
// Handle token form submission
if (tokenForm) {
tokenForm.addEventListener('submit', function(e) {
e.preventDefault();
const token = studentTokenInput.value.trim();
if (!token) {
alert('Please enter a token');
return;
}
// Send token to server for validation
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ token: token })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Store token in localStorage for persistence
localStorage.setItem('student_token', token);
// Update UI to show student info
studentInfoDiv.textContent = `Welcome, ${data.student_name}!`;
studentInfoDiv.style.display = 'block';
// Hide the form after successful login
tokenForm.style.display = 'none';
// Update the token input to show logged in state
studentTokenInput.placeholder = 'Logged in';
studentTokenInput.disabled = true;
// Add logout button
const logoutBtn = document.createElement('button');
logoutBtn.className = 'btn btn-outline-light';
logoutBtn.textContent = 'Logout';
logoutBtn.type = 'button';
logoutBtn.onclick = function() {
localStorage.removeItem('student_token');
// Clear the token input field
const tokenInput = document.getElementById('student-token');
if (tokenInput) {
tokenInput.value = '';
tokenInput.placeholder = 'Enter token';
tokenInput.disabled = false;
}
location.reload();
};
tokenForm.appendChild(logoutBtn);
} else {
alert(data.message || 'Invalid token');
}
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while logging in');
});
});
}
// Check if user is already logged in
const savedToken = localStorage.getItem('student_token');
if (savedToken) {
// Validate the saved token
fetch('/validate-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ token: savedToken })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update UI to show student info
studentInfoDiv.textContent = `Welcome, ${data.student_name}!`;
studentInfoDiv.style.display = 'block';
// Hide the form since user is already logged in
if (tokenForm) {
tokenForm.style.display = 'none';
// Add logout button
const logoutBtn = document.createElement('button');
logoutBtn.className = 'btn btn-outline-light';
logoutBtn.textContent = 'Logout';
logoutBtn.type = 'button';
logoutBtn.onclick = function() {
localStorage.removeItem('student_token');
// Clear the token input field
const tokenInput = document.getElementById('student-token');
if (tokenInput) {
tokenInput.value = '';
tokenInput.placeholder = 'Enter token';
tokenInput.disabled = false;
}
location.reload();
};
tokenForm.appendChild(logoutBtn);
}
} else {
// Token is invalid, remove it from localStorage
localStorage.removeItem('student_token');
}
})
.catch(error => {
console.error('Error validating token:', error);
});
}
});
</script>
</body>
</html>