Polish dashboard: consistent icon set, flatter cards, merged activity card
Replace card-header emoji with a matching stroke-style SVG icon set that inherits theme color via currentColor. Drop the accent-stripe border from every card and keep it only on the Your Courses card, the one primary action. Merge Continue Watching and Recently Viewed into a single true-recency list so an old completed item can no longer outrank a newer in-progress one across two separately-capped lists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+1
-20
@@ -1790,23 +1790,6 @@ def build_study_guide_markdown(course: Course) -> str:
|
|||||||
current_course = None
|
current_course = None
|
||||||
|
|
||||||
|
|
||||||
def _split_continue_watching(all_views: List[Dict[str, Any]]) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
|
||||||
"""
|
|
||||||
Split the recent-views list (already carrying percent_watched/completed,
|
|
||||||
see get_recent_views_for_display) into 'Continue Watching' - genuinely
|
|
||||||
in-progress lessons - and the remaining 'Recently Viewed' entries, each
|
|
||||||
capped at 5 for the dashboard. Reuses the same underlying history rather
|
|
||||||
than a separate library-wide progress index (see plan notes).
|
|
||||||
"""
|
|
||||||
continue_watching = [v for v in all_views if 0 < v['percent_watched'] < 100][:5]
|
|
||||||
continue_ids = {(v.get('course_path'), v.get('lesson_path')) for v in continue_watching}
|
|
||||||
recent_views = [
|
|
||||||
v for v in all_views
|
|
||||||
if (v.get('course_path'), v.get('lesson_path')) not in continue_ids
|
|
||||||
][:5]
|
|
||||||
return continue_watching, recent_views
|
|
||||||
|
|
||||||
|
|
||||||
def format_duration(seconds: int) -> str:
|
def format_duration(seconds: int) -> str:
|
||||||
"""Render a duration in seconds as e.g. '3h 24m' or '45m' (under an hour)."""
|
"""Render a duration in seconds as e.g. '3h 24m' or '45m' (under an hour)."""
|
||||||
total_minutes = max(0, int(seconds)) // 60
|
total_minutes = max(0, int(seconds)) // 60
|
||||||
@@ -1819,7 +1802,7 @@ def index():
|
|||||||
"""Main dashboard"""
|
"""Main dashboard"""
|
||||||
global current_course
|
global current_course
|
||||||
|
|
||||||
continue_watching, recent_views = _split_continue_watching(get_recent_views_for_display())
|
recent_views = get_recent_views_for_display()[:8]
|
||||||
|
|
||||||
if current_course is None:
|
if current_course is None:
|
||||||
# One scan covers stats/stale-courses/heatmap - see _scan_library_activity.
|
# One scan covers stats/stale-courses/heatmap - see _scan_library_activity.
|
||||||
@@ -1827,7 +1810,6 @@ def index():
|
|||||||
return render_template('course_dashboard.html',
|
return render_template('course_dashboard.html',
|
||||||
course=None,
|
course=None,
|
||||||
stats={'total_lessons': 0, 'completed_lessons': 0, 'completion_percentage': 0},
|
stats={'total_lessons': 0, 'completed_lessons': 0, 'completion_percentage': 0},
|
||||||
continue_watching=continue_watching,
|
|
||||||
recent_views=recent_views,
|
recent_views=recent_views,
|
||||||
recently_added=get_recently_added_courses(),
|
recently_added=get_recently_added_courses(),
|
||||||
library_stats=format_library_stats(scan),
|
library_stats=format_library_stats(scan),
|
||||||
@@ -1851,7 +1833,6 @@ def index():
|
|||||||
return render_template('course_dashboard.html',
|
return render_template('course_dashboard.html',
|
||||||
course=current_course,
|
course=current_course,
|
||||||
stats=stats,
|
stats=stats,
|
||||||
continue_watching=continue_watching,
|
|
||||||
recent_views=recent_views,
|
recent_views=recent_views,
|
||||||
has_note=bool(course_has_any_notes(current_course)),
|
has_note=bool(course_has_any_notes(current_course)),
|
||||||
is_queued=os.path.abspath(current_course.path) in get_next_up_paths(),
|
is_queued=os.path.abspath(current_course.path) in get_next_up_paths(),
|
||||||
|
|||||||
@@ -206,6 +206,12 @@
|
|||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#library-card {
|
||||||
|
/* the one card that's a primary action (browse/search/load a
|
||||||
|
course) rather than a passive info/list card - every other
|
||||||
|
card getting the same accent stripe made nothing stand out */
|
||||||
border-left: 4px solid var(--accent);
|
border-left: 4px solid var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,6 +386,16 @@
|
|||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-header-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.card-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.card-body {
|
.card-body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: 3000px;
|
max-height: 3000px;
|
||||||
@@ -1088,7 +1104,7 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="card" id="library-card">
|
<div class="card" id="library-card">
|
||||||
<h2>Your Courses</h2>
|
<h2><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="8" height="8" rx="1.5"></rect><rect x="13" y="3" width="8" height="8" rx="1.5"></rect><rect x="3" y="13" width="8" height="8" rx="1.5"></rect><rect x="13" y="13" width="8" height="8" rx="1.5"></rect></svg>Your Courses</span></h2>
|
||||||
<div class="library-toolbar">
|
<div class="library-toolbar">
|
||||||
<input type="text" id="library-search-input" class="library-search-input"
|
<input type="text" id="library-search-input" class="library-search-input"
|
||||||
placeholder="Search courses…" oninput="handleLibrarySearchInput(this.value)">
|
placeholder="Search courses…" oninput="handleLibrarySearchInput(this.value)">
|
||||||
@@ -1124,7 +1140,7 @@
|
|||||||
{% if library_stats and library_stats.total_courses %}
|
{% if library_stats and library_stats.total_courses %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="collapsible-header" data-card-id="library-stats">📊 Library Stats</h2>
|
<h2 class="collapsible-header" data-card-id="library-stats"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="20" x2="6" y2="14"></line><line x1="12" y1="20" x2="12" y2="8"></line><line x1="18" y1="20" x2="18" y2="11"></line></svg>Library Stats</span></h2>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stats-tile">
|
<div class="stats-tile">
|
||||||
@@ -1166,7 +1182,7 @@
|
|||||||
{% if next_up %}
|
{% if next_up %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="card" id="next-up-card">
|
<div class="card" id="next-up-card">
|
||||||
<h2 class="collapsible-header" data-card-id="next-up">📌 Next Up</h2>
|
<h2 class="collapsible-header" data-card-id="next-up"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="20" y2="6"></line><line x1="8" y1="12" x2="20" y2="12"></line><line x1="8" y1="18" x2="20" y2="18"></line><circle cx="4" cy="6" r="1"></circle><circle cx="4" cy="12" r="1"></circle><circle cx="4" cy="18" r="1"></circle></svg>Next Up</span></h2>
|
||||||
<div class="card-body" id="next-up-list">
|
<div class="card-body" id="next-up-list">
|
||||||
{% for item in next_up %}
|
{% for item in next_up %}
|
||||||
<div class="lesson-item" data-path="{{ item.path }}">
|
<div class="lesson-item" data-path="{{ item.path }}">
|
||||||
@@ -1193,7 +1209,7 @@
|
|||||||
{% if stale_courses %}
|
{% if stale_courses %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="collapsible-header" data-card-id="pick-back-up">⏰ Pick Back Up</h2>
|
<h2 class="collapsible-header" data-card-id="pick-back-up"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"></path><polyline points="3 4 3 9 8 9"></polyline></svg>Pick Back Up</span></h2>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% for item in stale_courses %}
|
{% for item in stale_courses %}
|
||||||
{{ render_course_row(item, 'Last touched ' + item.last_touched_display) }}
|
{{ render_course_row(item, 'Last touched ' + item.last_touched_display) }}
|
||||||
@@ -1205,7 +1221,7 @@
|
|||||||
{% if recently_added %}
|
{% if recently_added %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="collapsible-header" data-card-id="recently-added">🆕 Recently Added</h2>
|
<h2 class="collapsible-header" data-card-id="recently-added"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>Recently Added</span></h2>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% for item in recently_added %}
|
{% for item in recently_added %}
|
||||||
{{ render_course_row(item, item.added_display) }}
|
{{ render_course_row(item, item.added_display) }}
|
||||||
@@ -1214,22 +1230,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if continue_watching %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="card">
|
|
||||||
<h2 class="collapsible-header" data-card-id="continue-watching">▶ Continue Watching</h2>
|
|
||||||
<div class="card-body">
|
|
||||||
{% for view in continue_watching %}
|
|
||||||
{{ render_view_row(view) }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if recent_views %}
|
{% if recent_views %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="collapsible-header" data-card-id="recently-viewed">🕐 Recently Viewed</h2>
|
<h2 class="collapsible-header" data-card-id="recently-viewed"><span class="card-header-label"><svg class="card-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><polygon points="10 8 16 12 10 16 10 8"></polygon></svg>Recently Viewed</span></h2>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% for view in recent_views %}
|
{% for view in recent_views %}
|
||||||
{{ render_view_row(view) }}
|
{{ render_view_row(view) }}
|
||||||
|
|||||||
Reference in New Issue
Block a user