149 lines
5.6 KiB
JavaScript
149 lines
5.6 KiB
JavaScript
import { getJson, sendJson, sendJsonBlob } from './api.js';
|
|
|
|
export function createSlideController(deps) {
|
|
var previewSlides = [];
|
|
var previewIdx = 0;
|
|
|
|
function openSlidesFromSlug(slug) {
|
|
deps.showBusy('Loading slides...');
|
|
getJson('/api/learning/content/' + encodeURIComponent(slug) + '/slides')
|
|
.then(function(data) {
|
|
deps.hideBusy();
|
|
if (!data.success) { deps.showToast(data.error || 'Could not load slides', 'error'); return; }
|
|
openSlideModal(data.slides, data.css);
|
|
})
|
|
.catch(function(err) { deps.hideBusy(); deps.showToast(err.message, 'error'); });
|
|
}
|
|
|
|
function previewCurrentSlides() {
|
|
var md = deps.getMarkdown();
|
|
if (!md) { deps.showToast('No slide content yet. Use AI Generate first.', 'info'); return; }
|
|
|
|
deps.showBusy('Rendering slides...');
|
|
sendJson('/api/admin/learning/preview-slides', 'POST', { markdown: md })
|
|
.then(function(data) {
|
|
deps.hideBusy();
|
|
if (!data.success) { deps.showToast(data.error || 'Preview failed', 'error'); return; }
|
|
openSlideModal(data.slides, data.css);
|
|
})
|
|
.catch(function(err) { deps.hideBusy(); deps.showToast(err.message, 'error'); });
|
|
}
|
|
|
|
function openSlideModal(slides, css) {
|
|
closeSlideModal();
|
|
previewSlides = slides || [];
|
|
previewIdx = 0;
|
|
|
|
var modal = document.getElementById('slide-preview-modal');
|
|
var cssEl = document.getElementById('slide-preview-css');
|
|
var dotsEl = document.getElementById('slide-preview-dots');
|
|
|
|
if (!modal || !previewSlides.length) { deps.showToast('No slides to display', 'error'); return; }
|
|
if (cssEl) cssEl.textContent = css || '';
|
|
if (dotsEl) dotsEl.innerHTML = renderDots(previewSlides.length);
|
|
|
|
modal.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
renderSlide(0);
|
|
wireModalInput(modal);
|
|
}
|
|
|
|
function renderSlide(idx) {
|
|
var content = document.getElementById('slide-preview-content');
|
|
var counter = document.getElementById('slide-preview-counter');
|
|
var dots = document.querySelectorAll('.slide-dot');
|
|
var prev = document.getElementById('slide-nav-prev');
|
|
var next = document.getElementById('slide-nav-next');
|
|
|
|
if (!content || !previewSlides.length) return;
|
|
previewIdx = Math.max(0, Math.min(idx, previewSlides.length - 1));
|
|
|
|
content.innerHTML = previewSlides[previewIdx];
|
|
if (counter) counter.textContent = (previewIdx + 1) + ' / ' + previewSlides.length;
|
|
if (prev) prev.style.opacity = previewIdx === 0 ? '0.3' : '1';
|
|
if (next) next.style.opacity = previewIdx === previewSlides.length - 1 ? '0.3' : '1';
|
|
dots.forEach(function(d, i) { d.classList.toggle('active', i === previewIdx); });
|
|
}
|
|
|
|
function slideStep(dir) { renderSlide(previewIdx + dir); }
|
|
|
|
function closeSlideModal() {
|
|
var modal = document.getElementById('slide-preview-modal');
|
|
if (modal) modal.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
if (document._slideKeyHandler) {
|
|
document.removeEventListener('keydown', document._slideKeyHandler);
|
|
document._slideKeyHandler = null;
|
|
}
|
|
if (modal && modal._touchStart) {
|
|
modal.removeEventListener('touchstart', modal._touchStart);
|
|
modal._touchStart = null;
|
|
}
|
|
if (modal && modal._touchEnd) {
|
|
modal.removeEventListener('touchend', modal._touchEnd);
|
|
modal._touchEnd = null;
|
|
}
|
|
}
|
|
|
|
function downloadPptx() {
|
|
var md = deps.getMarkdown();
|
|
if (!md) { deps.showToast('No slide content yet', 'error'); return; }
|
|
var title = deps.getTitle() || 'presentation';
|
|
var btn = document.getElementById('btn-lh-download-pptx');
|
|
if (btn) { btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Building...'; }
|
|
|
|
sendJsonBlob('/api/admin/learning/generate-pptx', 'POST', { markdown: md, title: title })
|
|
.then(function(blob) {
|
|
var url = URL.createObjectURL(blob);
|
|
var a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = makePptxFilename(title);
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
deps.showToast('PPTX downloaded!', 'success');
|
|
})
|
|
.catch(function(err) { deps.showToast(err.message, 'error'); })
|
|
.finally(function() {
|
|
if (btn) { btn.disabled = false; btn.innerHTML = '<i class="fas fa-file-powerpoint"></i> Download PPTX'; }
|
|
});
|
|
}
|
|
|
|
function wireModalInput(modal) {
|
|
document._slideKeyHandler = function(e) {
|
|
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') slideStep(1);
|
|
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') slideStep(-1);
|
|
else if (e.key === 'Escape') closeSlideModal();
|
|
};
|
|
document.addEventListener('keydown', document._slideKeyHandler);
|
|
|
|
var touchStartX = 0;
|
|
modal._touchStart = function(e) { touchStartX = e.touches[0].clientX; };
|
|
modal._touchEnd = function(e) {
|
|
var dx = e.changedTouches[0].clientX - touchStartX;
|
|
if (Math.abs(dx) > 40) slideStep(dx < 0 ? 1 : -1);
|
|
};
|
|
modal.addEventListener('touchstart', modal._touchStart);
|
|
modal.addEventListener('touchend', modal._touchEnd);
|
|
}
|
|
|
|
return {
|
|
closeSlideModal: closeSlideModal,
|
|
downloadPptx: downloadPptx,
|
|
openSlidesFromSlug: openSlidesFromSlug,
|
|
previewCurrentSlides: previewCurrentSlides,
|
|
renderSlide: renderSlide,
|
|
slideStep: slideStep
|
|
};
|
|
}
|
|
|
|
export function makePptxFilename(title) {
|
|
return String(title || 'presentation').replace(/\s+/g, '-').toLowerCase() + '.pptx';
|
|
}
|
|
|
|
function renderDots(count) {
|
|
var html = '';
|
|
for (var i = 0; i < count; i++) {
|
|
html += '<button class="slide-dot" data-idx="' + i + '" aria-label="Go to slide ' + (i + 1) + '"></button>';
|
|
}
|
|
return html;
|
|
}
|