UI improved

This commit is contained in:
Harwinder Sekhon
2026-07-27 13:58:47 -04:00
parent e78f2a81e0
commit 31b01a1a18
11 changed files with 3257 additions and 412 deletions
+130 -11
View File
@@ -1,7 +1,11 @@
<?php
// GET api/library.php?view=home -> courses + continue-watching
// GET api/library.php?view=folder&id=N -> folder contents + progress
// GET api/library.php?view=video&id=N -> video + playlist + resume info
// GET api/library.php?view=meta -> sidebar/header chrome: totals, shelves, last scan
// GET api/library.php?view=home -> resume card, stats, in progress, recent additions
// GET api/library.php?view=library -> every course, grouped into shelves
// GET api/library.php?view=folder&id=N -> folder contents + progress
// GET api/library.php?view=video&id=N -> video + playlist + resume info
// GET api/library.php?view=search&q=text -> matching folders, videos, quizzes
// GET api/library.php?view=settings -> library paths, thresholds, progress summary
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/functions.php';
@@ -53,19 +57,83 @@ function crumbList(array $folder): array
);
}
/** The three shelves the library groups courses into. */
function shelveCourses(array $courses): array
{
$shelves = [
['key' => 'progress', 'name' => 'In progress', 'courses' => []],
['key' => 'finished', 'name' => 'Finished', 'courses' => []],
['key' => 'fresh', 'name' => 'Not started', 'courses' => []],
];
foreach ($courses as $c) {
foreach ($shelves as $i => $shelf) {
if ($shelf['key'] === $c['state']) {
$shelves[$i]['courses'][] = $c;
}
}
}
return array_values(array_filter($shelves, fn($s) => $s['courses']));
}
$view = $_GET['view'] ?? 'home';
switch ($view) {
case 'home':
case 'meta':
$courses = courseCards($userId);
$counts = ['progress' => 0, 'finished' => 0, 'fresh' => 0];
foreach ($courses as $c) {
$counts[$c['state']]++;
}
echo json_encode([
'courses' => array_map(fn($c) => folderSummary($c, $userId), childFolders(null)),
'continue' => array_map(fn($r) => [
'totals' => libraryTotals(),
'shelves' => [
['key' => 'progress', 'name' => 'In progress', 'count' => $counts['progress']],
['key' => 'finished', 'name' => 'Finished', 'count' => $counts['finished']],
['key' => 'fresh', 'name' => 'Not started', 'count' => $counts['fresh']],
],
'last_scan' => appMetaGet('last_scan_at'),
'username' => currentUsername(),
]);
break;
case 'home':
$courses = courseCards($userId);
$resume = continueWatching($userId, 1);
$resumeCourseId = null;
if ($resume) {
$resumeFolder = getFolder((int)$resume[0]['folder_id']);
if ($resumeFolder) $resumeCourseId = courseIdOf($resumeFolder);
}
echo json_encode([
'stats' => userStats($userId, $courses),
'resume' => $resume ? [
'id' => (int)$resume[0]['id'],
'course_id' => $resumeCourseId,
'name' => $resume[0]['name'],
'folder_path' => $resume[0]['folder_path'],
'position' => (float)$resume[0]['position_seconds'],
'duration' => $resume[0]['duration_seconds'] !== null
? (float)$resume[0]['duration_seconds'] : null,
] : null,
'continue' => array_map(fn($r) => [
'id' => (int)$r['id'],
'name' => $r['name'],
'folder_path' => $r['folder_path'],
'position' => (float)$r['position_seconds'],
'duration' => $r['duration_seconds'] !== null ? (float)$r['duration_seconds'] : null,
], continueWatching($userId)),
'in_progress' => array_values(array_filter($courses, fn($c) => $c['state'] === 'progress')),
'recent' => recentCourses($userId),
'courses' => $courses,
]);
break;
case 'library':
$courses = courseCards($userId);
echo json_encode([
'courses' => $courses,
'shelves' => shelveCourses($courses),
'totals' => libraryTotals(),
]);
break;
@@ -76,11 +144,15 @@ switch ($view) {
echo json_encode(['error' => 'Folder not found']);
break;
}
$crumbs = crumbList($folder);
echo json_encode([
'folder' => ['id' => (int)$folder['id'], 'name' => $folder['name']],
'breadcrumbs' => crumbList($folder),
'folder' => ['id' => (int)$folder['id'], 'name' => $folder['name'],
'path' => $folder['path']],
'course_id' => $crumbs ? $crumbs[0]['id'] : (int)$folder['id'],
'breadcrumbs' => $crumbs,
'rollup' => folderRollup($userId, $folder['path']),
'progress' => folderProgress($userId, $folder['path']),
'subfolders' => array_map(fn($f) => folderSummary($f, $userId),
'subfolders' => array_map(fn($f) => courseSummary($userId, $f),
childFolders((int)$folder['id'])),
'videos' => array_map('videoSummary',
folderVideos((int)$folder['id'], $userId)),
@@ -98,6 +170,7 @@ switch ($view) {
}
$folder = getFolder((int)$video['folder_id']);
$playlist = folderVideos((int)$folder['id'], $userId);
$crumbs = crumbList($folder);
$stmt = db()->prepare(
'SELECT position_seconds, completed FROM video_progress
@@ -118,10 +191,14 @@ switch ($view) {
}
echo json_encode([
'video' => ['id' => (int)$video['id'], 'name' => $video['name']],
'video' => ['id' => (int)$video['id'], 'name' => $video['name'],
'duration' => $video['duration_seconds'] !== null
? (float)$video['duration_seconds'] : null],
'folder' => ['id' => (int)$folder['id'], 'name' => $folder['name']],
'breadcrumbs' => crumbList($folder),
'course_id' => $crumbs ? $crumbs[0]['id'] : (int)$folder['id'],
'breadcrumbs' => $crumbs,
'playlist' => array_map('videoSummary', $playlist),
'quizzes' => array_map('quizSummary', folderQuizzes((int)$folder['id'], $userId)),
'resume_at' => $resumeAt,
'prev_id' => $prevId,
'next_id' => $nextId,
@@ -129,6 +206,48 @@ switch ($view) {
]);
break;
case 'search':
$q = trim((string)($_GET['q'] ?? ''));
if ($q === '') {
echo json_encode(['query' => '', 'folders' => [], 'videos' => [], 'quizzes' => []]);
break;
}
$withPath = fn(array $row, array $summary) =>
$summary + ['folder_path' => $row['folder_path']];
echo json_encode([
'query' => $q,
'folders' => array_map(
fn($f) => courseSummary($userId, $f),
searchFolders($q)
),
'videos' => array_map(
fn($v) => $withPath($v, videoSummary($v)),
searchVideos($userId, $q)
),
'quizzes' => array_map(
fn($q2) => $withPath($q2, quizSummary($q2)),
searchQuizzes($userId, $q)
),
]);
break;
case 'settings':
$courses = courseCards($userId);
$withSome = array_values(array_filter($courses, fn($c) => $c['progress']['percent'] > 0));
echo json_encode([
'root' => rtrim(VIDEOS_ROOT, '/'),
'root_exists' => is_dir(VIDEOS_ROOT),
'totals' => libraryTotals(),
'last_scan' => appMetaGet('last_scan_at'),
'complete_threshold' => (int)round(100 * COMPLETE_THRESHOLD),
'registration_open' => (bool)ALLOW_REGISTRATION,
'username' => currentUsername(),
'progress_courses' => count($withSome),
'courses' => count($courses),
]);
break;
default:
http_response_code(400);
echo json_encode(['error' => 'Unknown view']);