mirror of
https://github.com/blshaer/python-by-example.git
synced 2026-03-28 07:39:24 +01:00
354 lines
14 KiB
JavaScript
354 lines
14 KiB
JavaScript
const documentation = [
|
|
{
|
|
id: "01_basics",
|
|
title: "01. Python Basics",
|
|
description: "Fundamental syntax and data types.",
|
|
icon: "fas fa-seedling",
|
|
lessons: [
|
|
{ title: "Print Function", file: "01_basics/01_print.py" },
|
|
{ title: "Comments", file: "01_basics/02_comments.py" },
|
|
{ title: "Variables", file: "01_basics/03_variables.py" },
|
|
{ title: "Data Types", file: "01_basics/04_data_types.py" }
|
|
]
|
|
},
|
|
{
|
|
id: "02_control_flow",
|
|
title: "02. Control Flow",
|
|
description: "Conditional logic and selection.",
|
|
icon: "fas fa-code-branch",
|
|
lessons: [
|
|
{ title: "If-Else", file: "02_control_flow/01_if_else.py" },
|
|
{ title: "Elif", file: "02_control_flow/02_elif.py" },
|
|
{ title: "Match Case", file: "02_control_flow/03_match_case.py" }
|
|
]
|
|
},
|
|
{
|
|
id: "03_loops",
|
|
title: "03. Loops & Iterations",
|
|
description: "Repetitive task automation.",
|
|
icon: "fas fa-sync",
|
|
lessons: [
|
|
{ title: "For Loops", file: "03_loops/01_for_loop.py" },
|
|
{ title: "While Loops", file: "03_loops/02_while_loop.py" },
|
|
{ title: "Break & Continue", file: "03_loops/03_break_continue.py" }
|
|
]
|
|
},
|
|
{
|
|
id: "04_data_structures",
|
|
title: "04. Data Structures",
|
|
description: "Collections and data management.",
|
|
icon: "fas fa-database",
|
|
lessons: [
|
|
{ title: "Lists", file: "04_data_structures/01_lists.py" },
|
|
{ title: "Tuples", file: "04_data_structures/02_tuples.py" },
|
|
{ title: "Sets", file: "04_data_structures/03_sets.py" },
|
|
{ title: "Dictionaries", file: "04_data_structures/04_dictionaries.py" }
|
|
]
|
|
},
|
|
{
|
|
id: "05_functions",
|
|
title: "05. Functions",
|
|
description: "Modular and reusable blocks.",
|
|
icon: "fas fa-cube",
|
|
lessons: [
|
|
{ title: "Function Basics", file: "05_functions/01_function_basics.py" },
|
|
{ title: "Arguments", file: "05_functions/02_arguments.py" },
|
|
{ title: "Return Values", file: "05_functions/03_return_values.py" },
|
|
{ title: "Lambda Functions", file: "05_functions/04_lambda_functions.py" }
|
|
]
|
|
},
|
|
{
|
|
id: "06_modules_packages",
|
|
title: "06. Modules & Packages",
|
|
description: "Code organization and imports.",
|
|
icon: "fas fa-box-open",
|
|
lessons: [
|
|
{ title: "Imports", file: "06_modules_packages/01_imports.py" },
|
|
{ title: "Custom Modules", file: "06_modules_packages/02_custom_modules.py" }
|
|
]
|
|
},
|
|
{
|
|
id: "07_error_handling",
|
|
title: "07. Error Handling",
|
|
description: "Robustness and exception safety.",
|
|
icon: "fas fa-exclamation-triangle",
|
|
lessons: [
|
|
{ title: "Try-Except", file: "07_error_handling/01_try_except.py" },
|
|
{ title: "Custom Exceptions", file: "07_error_handling/02_custom_exceptions.py" }
|
|
]
|
|
},
|
|
{
|
|
id: "08_oop",
|
|
title: "08. Object Oriented Programming",
|
|
description: "Classes, objects, and inheritance.",
|
|
icon: "fas fa-project-diagram",
|
|
lessons: [
|
|
{ title: "Classes & Objects", file: "08_oop/01_classes_objects.py" },
|
|
{ title: "Init Methods", file: "08_oop/02_init_methods.py" },
|
|
{ title: "Inheritance", file: "08_oop/03_inheritance.py" },
|
|
{ title: "Polymorphism", file: "08_oop/04_polymorphism.py" }
|
|
]
|
|
},
|
|
{
|
|
id: "09_advanced_python",
|
|
title: "09. Advanced Python",
|
|
description: "Generators, decorators, and more.",
|
|
icon: "fas fa-bolt",
|
|
lessons: [
|
|
{ title: "List Comprehensions", file: "09_advanced_python/01_list_comprehensions.py" },
|
|
{ title: "Generators", file: "09_advanced_python/02_generators.py" },
|
|
{ title: "Decorators", file: "09_advanced_python/03_decorators.py" },
|
|
{ title: "Context Managers", file: "09_advanced_python/04_context_managers.py" }
|
|
]
|
|
},
|
|
{
|
|
id: "10_best_practices",
|
|
title: "10. Best Practices",
|
|
description: "Clean code and professional PEP 8.",
|
|
icon: "fas fa-check-double",
|
|
lessons: [
|
|
{ title: "PEP 8", file: "10_best_practices/01_pep8.py" },
|
|
{ title: "Type Hinting", file: "10_best_practices/02_type_hinting.py" },
|
|
{ title: "Virtual Envs", file: "10_best_practices/03_virtual_envs.py" }
|
|
]
|
|
}
|
|
];
|
|
|
|
let pyodideInstance = null;
|
|
let editor = null;
|
|
let originalCode = "";
|
|
|
|
// Initialize Monaco Editor
|
|
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.44.0/min/vs' } });
|
|
require(['vs/editor/editor.main'], function () {
|
|
editor = monaco.editor.create(document.getElementById('monaco-editor-container'), {
|
|
value: "# Select a lesson to begin...",
|
|
language: 'python',
|
|
theme: 'vs-dark',
|
|
automaticLayout: true,
|
|
readOnly: true,
|
|
fontSize: 14,
|
|
minimap: { enabled: false },
|
|
lineNumbers: 'on',
|
|
scrollBeyondLastLine: false,
|
|
padding: { top: 20 }
|
|
});
|
|
});
|
|
|
|
async function initPyodide() {
|
|
const outputArea = document.getElementById('output-area');
|
|
try {
|
|
pyodideInstance = await loadPyodide();
|
|
outputArea.textContent = ">>> Python 3.10 (Pyodide) Ready.\n";
|
|
document.getElementById('run-btn').disabled = false;
|
|
} catch (err) {
|
|
outputArea.textContent = "Error loading Pyodide: " + err.message;
|
|
}
|
|
}
|
|
|
|
async function runPython(code) {
|
|
const outputArea = document.getElementById('output-area');
|
|
|
|
if (!pyodideInstance) {
|
|
outputArea.textContent = ">>> Error: Python environment not yet initialized. Please wait.\n";
|
|
return;
|
|
}
|
|
|
|
outputArea.textContent = "Running...\n";
|
|
|
|
try {
|
|
pyodideInstance.setStdout({
|
|
batched: (text) => {
|
|
outputArea.textContent += text + "\n";
|
|
}
|
|
});
|
|
|
|
await pyodideInstance.runPythonAsync(code);
|
|
outputArea.textContent += "\n[Execution Complete]";
|
|
} catch (err) {
|
|
outputArea.textContent += "\nTraceback (most recent call last):\n" + err.message;
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initPyodide();
|
|
|
|
const content = document.getElementById('documentation-content');
|
|
const toc = document.getElementById('toc');
|
|
const modal = document.getElementById('playground-modal');
|
|
const lessonTitle = document.getElementById('lesson-title');
|
|
const resetBtn = document.getElementById('reset-btn');
|
|
const editBtn = document.getElementById('edit-btn');
|
|
const runBtn = document.getElementById('run-btn');
|
|
const closeModal = document.querySelector('.close-modal');
|
|
|
|
content.innerHTML = '';
|
|
toc.innerHTML = '';
|
|
|
|
// Render Documentation
|
|
documentation.forEach((module) => {
|
|
const section = document.createElement('section');
|
|
section.id = module.id;
|
|
section.className = 'doc-section';
|
|
|
|
section.innerHTML = `
|
|
<h2><i class="${module.icon || 'fas fa-book'}" style="margin-right: 12px; font-size: 1.2rem; color: var(--accent);"></i>${module.title}</h2>
|
|
<p>${module.description}</p>
|
|
<div class="module-card">
|
|
<ul class="lesson-list" style="list-style: none; padding-left: 0;">
|
|
${module.lessons.map(lesson => `
|
|
<li class="lesson-item" data-file="${lesson.file}" data-title="${lesson.title}"
|
|
style="padding: 12px; border-bottom: 1px solid var(--border-color); display: flex; align-items: center; justify-content: space-between; cursor: pointer; transition: 0.2s; border-radius: 6px;">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<i class="fab fa-python" style="color: var(--accent);"></i>
|
|
<span>${lesson.title}</span>
|
|
</div>
|
|
<span style="font-size: 0.8rem; color: var(--text-secondary);">Run Live <i class="fas fa-play" style="font-size: 0.6rem;"></i></span>
|
|
</li>
|
|
`).join('')}
|
|
</ul>
|
|
</div>
|
|
`;
|
|
content.appendChild(section);
|
|
|
|
const tocLink = document.createElement('a');
|
|
tocLink.href = `#${module.id}`;
|
|
tocLink.className = 'toc-link';
|
|
tocLink.textContent = module.title;
|
|
toc.appendChild(tocLink);
|
|
});
|
|
|
|
// Handle Lesson Clicks
|
|
document.addEventListener('click', async (e) => {
|
|
const item = e.target.closest('.lesson-item');
|
|
if (!item) return;
|
|
|
|
const file = item.getAttribute('data-file');
|
|
const title = item.getAttribute('data-title');
|
|
lessonTitle.textContent = `${title}.py`;
|
|
modal.classList.add('active');
|
|
|
|
// Monaco needs layout() called when container becomes visible to display cursor/suggestions correctly
|
|
setTimeout(() => {
|
|
if (editor) {
|
|
editor.layout();
|
|
editor.focus();
|
|
}
|
|
}, 100);
|
|
|
|
// Reset edit mode
|
|
editBtn.classList.remove('active');
|
|
editBtn.innerHTML = '<i class="fas fa-edit"></i> Edit';
|
|
if (editor) editor.updateOptions({ readOnly: true });
|
|
|
|
if (editor) editor.setValue("# Loading source code...");
|
|
|
|
try {
|
|
const response = await fetch(file);
|
|
if (!response.ok) throw new Error("File not found");
|
|
const code = await response.text();
|
|
originalCode = code;
|
|
if (editor) {
|
|
editor.setValue(code);
|
|
// Force suggestions/intellisense refresh
|
|
monaco.editor.setModelLanguage(editor.getModel(), 'python');
|
|
}
|
|
} catch (err) {
|
|
const fallback = `# Error loading file: ${file}\n# serving from github.io might require valid paths.\n\nprint("Hello from Python By Example!")\nname = "Developer"\nprint(f"Happy coding, {name}!")`;
|
|
originalCode = fallback;
|
|
if (editor) editor.setValue(fallback);
|
|
}
|
|
});
|
|
|
|
// Edit Toggle
|
|
editBtn.addEventListener('click', () => {
|
|
if (!editor) return;
|
|
const isEditing = editBtn.classList.toggle('active');
|
|
editor.updateOptions({ readOnly: !isEditing });
|
|
|
|
if (isEditing) {
|
|
editBtn.innerHTML = '<i class="fas fa-check"></i> Editing...';
|
|
editor.focus();
|
|
document.getElementById('output-area').textContent = ">>> Editing mode enabled. You can now modify the code and see suggestions.\n";
|
|
} else {
|
|
editBtn.innerHTML = '<i class="fas fa-edit"></i> Edit';
|
|
document.getElementById('output-area').textContent = ">>> Editing mode disabled.\n";
|
|
}
|
|
});
|
|
|
|
// Reset Logic
|
|
resetBtn.addEventListener('click', () => {
|
|
if (!editor) return;
|
|
editor.setValue(originalCode);
|
|
editBtn.classList.remove('active');
|
|
editor.updateOptions({ readOnly: true });
|
|
editBtn.innerHTML = '<i class="fas fa-edit"></i> Edit';
|
|
document.getElementById('output-area').textContent = ">>> Code reset to original state.\n";
|
|
});
|
|
|
|
// Run Logic
|
|
runBtn.addEventListener('click', () => {
|
|
if (editor) runPython(editor.getValue());
|
|
});
|
|
|
|
// Modal Controls
|
|
closeModal.addEventListener('click', () => modal.classList.remove('active'));
|
|
modal.addEventListener('click', (e) => {
|
|
if (e.target === modal) modal.classList.remove('active');
|
|
});
|
|
|
|
// Search Functionality (Improved)
|
|
const searchInput = document.querySelector('.search-box input');
|
|
searchInput.addEventListener('input', (e) => {
|
|
const query = e.target.value.toLowerCase().trim();
|
|
|
|
document.querySelectorAll('.doc-section').forEach(section => {
|
|
const heading = section.querySelector('h2').textContent.toLowerCase();
|
|
const desc = section.querySelector('p').textContent.toLowerCase();
|
|
const lessonItems = section.querySelectorAll('.lesson-item');
|
|
|
|
let isSectionVisible = query === "" || heading.includes(query) || desc.includes(query);
|
|
let matchingLessonsCount = 0;
|
|
|
|
lessonItems.forEach(item => {
|
|
const lessonText = item.textContent.toLowerCase();
|
|
const isLessonMatch = query === "" || lessonText.includes(query);
|
|
|
|
// Show lesson if it matches or if the section title matches
|
|
const shouldShowLesson = isSectionVisible || isLessonMatch;
|
|
item.style.setProperty('display', shouldShowLesson ? 'flex' : 'none', 'important');
|
|
|
|
if (isLessonMatch) matchingLessonsCount++;
|
|
});
|
|
|
|
// Show section if title matches or if any lesson inside matches
|
|
section.style.display = (isSectionVisible || matchingLessonsCount > 0) ? 'block' : 'none';
|
|
});
|
|
|
|
// Sync TOC
|
|
document.querySelectorAll('.toc-link').forEach(link => {
|
|
const targetId = link.getAttribute('href').substring(1);
|
|
const targetSection = document.getElementById(targetId);
|
|
if (targetSection) {
|
|
link.style.display = targetSection.style.display === 'none' ? 'none' : 'block';
|
|
}
|
|
});
|
|
});
|
|
|
|
// Scroll Spy
|
|
window.addEventListener('scroll', () => {
|
|
let current = '';
|
|
document.querySelectorAll('.doc-section').forEach(section => {
|
|
if (section.style.display !== 'none' && window.pageYOffset >= (section.offsetTop - 150)) {
|
|
current = section.getAttribute('id');
|
|
}
|
|
});
|
|
document.querySelectorAll('.toc-link').forEach(link => {
|
|
link.classList.remove('active');
|
|
if (link.style.display !== 'none' && link.getAttribute('href').substring(1) === current) {
|
|
link.classList.add('active');
|
|
}
|
|
});
|
|
});
|
|
});
|