- Course outline sidebar now sits on the left of the player instead of the right (mobile stacking order unchanged - video still first). - Collapsible to a slim rail via a toggle button, remembered per device, for a full-width player when wanted. - Which sections a user has manually expanded is now remembered (keyed by the section's path) and shared between the lesson-page sidebar and the dashboard's course view, so re-opening a course doesn't reset everything back to collapsed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
79 lines
4.4 KiB
HTML
79 lines
4.4 KiB
HTML
{% import '_icons.html' as icons %}
|
|
{#
|
|
Shared recursive course-outline renderer - one section/lesson tree
|
|
markup used by both the loaded-course dashboard view and the lesson
|
|
page's sidebar, so the two stay visually and behaviorally in sync
|
|
instead of drifting apart as separate copies. Needs `toggleTree()`
|
|
(JS) and the .tree-*/.lesson-*/.status-icon/.watched-badge CSS to be
|
|
defined by whichever page imports this - see course_dashboard.html
|
|
for the canonical versions.
|
|
|
|
`current_lesson` (a Lesson object, not a path string) is optional -
|
|
when given, the matching row gets a `current` class instead of being
|
|
a plain click target, and stays unclickable since you're already
|
|
there. Path-string comparison isn't used for this because the lesson
|
|
page's incoming URL can be in several different formats (see
|
|
find_lesson_in_tree) - comparing lesson.path (the raw absolute
|
|
filesystem path, always unique and unambiguous) sidesteps all of that.
|
|
#}
|
|
{% macro render_tree_node(node, course_path, current_lesson=none, depth=0) %}
|
|
{% set stats = section_stats(node) %}
|
|
<div class="tree-item">
|
|
<div class="tree-header directory" onclick="toggleTree(this)">
|
|
<div class="tree-title">
|
|
<span class="tree-icon">{{ icons.icon('folder', 16) }}</span>
|
|
<span class="tree-name" title="{{ node.name }}">{{ node.name }}</span>
|
|
<span class="tree-stats">
|
|
{% if stats.total_lessons %}{{ stats.completed_lessons }}/{{ stats.total_lessons }} watched{% else %}Empty{% endif %}
|
|
</span>
|
|
</div>
|
|
{% if node.children or node.lessons %}
|
|
<button class="tree-toggle">▶</button>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if node.children or node.lessons %}
|
|
<div class="tree-content" data-section-path="{{ node.path }}">
|
|
{% for child_name, child_node in node.children.items() %}
|
|
{{ render_tree_node(child_node, course_path, current_lesson, depth + 1) }}
|
|
{% endfor %}
|
|
|
|
{% for lesson in node.lessons %}
|
|
{% set lesson_relative_path = lesson.path|replace('\\', '/')|replace(course_path|replace('\\', '/'), '')|replace('//', '/')|replace('/', '', 1) %}
|
|
{% set percent_watched = 100 if lesson.completed else (((100 * lesson.progress_seconds / lesson.duration_seconds)|round|int) if (lesson.duration_seconds and lesson.progress_seconds) else 0) %}
|
|
{% set is_current = current_lesson and lesson.path == current_lesson.path %}
|
|
<div class="lesson-item {% if is_current %}current{% elif lesson.completed %}completed{% elif percent_watched %}in-progress{% endif %}"
|
|
{% if not is_current %}onclick="window.location.href='/lesson/{{ lesson_relative_path }}/{{ lesson.title|replace(' ', '_') }}'"{% endif %}>
|
|
<div class="lesson-title">
|
|
<span class="lesson-icon">
|
|
{% if lesson.lesson_type == 'video' %}{{ icons.icon('video', 16) }}
|
|
{% elif lesson.lesson_type == 'audio' %}{{ icons.icon('music', 16) }}
|
|
{% elif lesson.lesson_type == 'quiz' %}{{ icons.icon('clipboard', 16) }}
|
|
{% elif lesson.lesson_type == 'mixed' %}{{ icons.icon('package', 16) }}
|
|
{% else %}{{ icons.icon('file-text', 16) }}{% endif %}
|
|
</span>
|
|
<span class="lesson-name" title="{{ lesson.title }}">{{ lesson.title }}</span>
|
|
</div>
|
|
<div class="lesson-meta">
|
|
<span class="lesson-type {{ lesson.lesson_type }}">{{ lesson.lesson_type|title }}</span>
|
|
{% if lesson.duration_seconds and lesson.duration_seconds >= 60 %}
|
|
<span class="lesson-duration">{{ lesson.duration_seconds|format_duration }}</span>
|
|
{% endif %}
|
|
{% if lesson.completed %}
|
|
<span class="status-icon completed">{{ icons.icon('check', 14) }}</span>
|
|
{% elif percent_watched %}
|
|
<span class="watched-badge">{{ percent_watched }}% watched</span>
|
|
{% else %}
|
|
<span class="status-icon pending">{{ icons.icon('circle', 14) }}</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if percent_watched %}
|
|
<div class="lesson-progress-track"><div class="lesson-progress-fill" style="width: {{ percent_watched }}%;"></div></div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endmacro %}
|