New main page way of browsing the courses
This commit is contained in:
@@ -680,55 +680,84 @@
|
||||
function loadLibrary() {
|
||||
const libraryCard = document.getElementById('library-card');
|
||||
if (!libraryCard) return;
|
||||
fetchLibraryLevel(null, document.getElementById('library-groups'), true);
|
||||
}
|
||||
|
||||
fetch('/library')
|
||||
function fetchLibraryLevel(path, container, isRoot) {
|
||||
const url = path ? `/library?path=${encodeURIComponent(path)}` : '/library';
|
||||
fetch(url)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
document.getElementById('library-path-bar').textContent =
|
||||
`Scanning ${data.library_path}`;
|
||||
|
||||
const container = document.getElementById('library-groups');
|
||||
const groupNames = Object.keys(data.groups).sort((a, b) => a.localeCompare(b));
|
||||
|
||||
if (groupNames.length === 0) {
|
||||
const reason = (data.errors && data.errors.length)
|
||||
? data.errors.join(' ')
|
||||
: `No course folders found under ${data.library_path}.`;
|
||||
container.innerHTML = `<p style="color:#999;">${reason} You can still load a course by path below.</p>`;
|
||||
return;
|
||||
if (isRoot) {
|
||||
document.getElementById('library-path-bar').textContent =
|
||||
`Scanning ${data.library_path}`;
|
||||
}
|
||||
|
||||
container.innerHTML = groupNames.map((group, i) => `
|
||||
<div class="tree-item">
|
||||
<div class="tree-header directory" onclick="toggleTree(this)">
|
||||
<div class="tree-title">
|
||||
<span class="tree-icon">📁</span>
|
||||
<span class="tree-name">${group}</span>
|
||||
<span class="tree-stats">${data.groups[group].length} course${data.groups[group].length === 1 ? '' : 's'}</span>
|
||||
</div>
|
||||
<button class="tree-toggle">▶</button>
|
||||
</div>
|
||||
<div class="tree-content">
|
||||
${data.groups[group].map(course => `
|
||||
<div class="lesson-item"
|
||||
onclick="loadCoursePath('${course.path.replace(/'/g, "\\'")}')">
|
||||
<div class="lesson-title">
|
||||
<span class="lesson-icon">🎓</span>
|
||||
<span>${course.name}</span>
|
||||
</div>
|
||||
<span class="lesson-meta">${course.media_files} media file${course.media_files === 1 ? '' : 's'}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
renderLibraryLevel(data, container, isRoot);
|
||||
})
|
||||
.catch(() => {
|
||||
document.getElementById('library-groups').innerHTML =
|
||||
container.innerHTML =
|
||||
'<p style="color:#ff6b6b;">Could not reach the library scanner.</p>';
|
||||
});
|
||||
}
|
||||
|
||||
function renderLibraryLevel(data, container, isRoot) {
|
||||
if (!data.items || data.items.length === 0) {
|
||||
const reason = (data.errors && data.errors.length)
|
||||
? data.errors.join(' ')
|
||||
: (isRoot ? `No course folders found under ${data.library_path}.` : 'No courses in here.');
|
||||
container.innerHTML = `<p style="color:#999; padding: 6px 0;">${reason}${isRoot ? ' You can still load a course by path below.' : ''}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = data.items.map(item => {
|
||||
if (item.type === 'course') {
|
||||
return `
|
||||
<div class="lesson-item"
|
||||
onclick="loadCoursePath('${item.path.replace(/'/g, "\\'")}')">
|
||||
<div class="lesson-title">
|
||||
<span class="lesson-icon">🎓</span>
|
||||
<span>${item.name}</span>
|
||||
</div>
|
||||
<span class="lesson-meta">${item.media_files} media file${item.media_files === 1 ? '' : 's'}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return `
|
||||
<div class="tree-item">
|
||||
<div class="tree-header directory" onclick="toggleLibraryDir(this, '${item.path.replace(/'/g, "\\'")}')">
|
||||
<div class="tree-title">
|
||||
<span class="tree-icon">📁</span>
|
||||
<span class="tree-name">${item.name}</span>
|
||||
</div>
|
||||
<button class="tree-toggle">▶</button>
|
||||
</div>
|
||||
<div class="tree-content" data-loaded="false"></div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function toggleLibraryDir(headerEl, path) {
|
||||
const content = headerEl.nextElementSibling;
|
||||
const toggle = headerEl.querySelector('.tree-toggle');
|
||||
if (!content || !content.classList.contains('tree-content')) return;
|
||||
|
||||
if (content.classList.contains('expanded')) {
|
||||
content.classList.remove('expanded');
|
||||
toggle.textContent = '▶';
|
||||
return;
|
||||
}
|
||||
|
||||
content.classList.add('expanded');
|
||||
toggle.textContent = '▼';
|
||||
|
||||
if (content.dataset.loaded === 'true') return; // already fetched this branch
|
||||
|
||||
content.innerHTML = '<p style="color:#999; padding: 6px 0;">Loading...</p>';
|
||||
content.dataset.loaded = 'true';
|
||||
fetchLibraryLevel(path, content, false);
|
||||
}
|
||||
|
||||
function loadCoursePath(path) {
|
||||
fetch('/load_course', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -312,6 +312,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Library</h2>
|
||||
<div class="setting-row" style="flex-direction: column; align-items: stretch; gap: 8px;">
|
||||
<label for="library_path">Default courses directory
|
||||
<span class="setting-desc">Where the Library browser starts. Leave blank to use the server default ({{ default_library_path }}).</span>
|
||||
</label>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<input type="text" id="library_path" class="hex-input" style="flex: 1; width: auto; font-family: var(--font-family);"
|
||||
placeholder="{{ default_library_path }}" value="{{ settings.library_path }}">
|
||||
<button class="btn" onclick="saveLibraryPath()">Save</button>
|
||||
</div>
|
||||
<span id="library-path-status" style="font-size: 0.85em; min-height: 1.2em;"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn btn-secondary" onclick="resetSettings()">Reset to Defaults</button>
|
||||
<span id="save-status">✓ Saved</span>
|
||||
@@ -380,6 +395,31 @@
|
||||
}
|
||||
});
|
||||
|
||||
function saveLibraryPath() {
|
||||
const input = document.getElementById('library_path');
|
||||
const status = document.getElementById('library-path-status');
|
||||
fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ library_path: input.value.trim() })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
status.style.color = '#28a745';
|
||||
status.textContent = '✓ Saved — reload the main page to see it take effect';
|
||||
showSaved();
|
||||
} else {
|
||||
status.style.color = '#ff6b6b';
|
||||
status.textContent = data.error || 'Could not save';
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
status.style.color = '#ff6b6b';
|
||||
status.textContent = 'Could not reach the server';
|
||||
});
|
||||
}
|
||||
|
||||
function resetSettings() {
|
||||
fetch('/api/settings/reset', { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
|
||||
Reference in New Issue
Block a user