Files
Teleprompter/renderer.js
T
2025-12-12 14:05:16 +01:00

147 lines
4.2 KiB
JavaScript

const btnHeroOpen = document.getElementById('btn-hero-open');
const btnOpen = document.getElementById('btn-open');
const btnPlay = document.getElementById('btn-play');
const btnReset = document.getElementById('btn-reset');
const inputSpeed = document.getElementById('input-speed');
const inputSize = document.getElementById('input-size');
const markdownOutput = document.getElementById('markdown-output');
const placeholder = document.getElementById('placeholder');
const scrollContainer = document.getElementById('scroll-container');
const iconPlay = document.getElementById('icon-play');
const iconPause = document.getElementById('icon-pause');
let isScrolling = false;
let scrollAccumulator = 0;
let animationId;
let scrollSpeed = parseInt(inputSpeed.value, 10);
// Initialize
function init() {
setupEventListeners();
updateFontSize(inputSize.value);
}
function setupEventListeners() {
btnHeroOpen.addEventListener('click', openFile);
btnOpen.addEventListener('click', openFile);
btnPlay.addEventListener('click', toggleScrolling);
// Spacebar to toggle
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
e.preventDefault(); // Prevent page down scroll
toggleScrolling();
}
});
btnReset.addEventListener('click', () => {
stopScrolling();
scrollContainer.scrollTo({ top: 0, behavior: 'smooth' });
});
inputSpeed.addEventListener('input', (e) => {
scrollSpeed = parseInt(e.target.value, 10);
});
inputSize.addEventListener('input', (e) => {
updateFontSize(e.target.value);
});
}
async function openFile() {
const filePath = await window.electronAPI.openFileDialog();
if (filePath) {
const content = await window.electronAPI.readFile(filePath);
renderMarkdown(content);
}
}
function renderMarkdown(content) {
if (!content) return;
// Using marked from global scope (loaded via script tag)
const html = marked.parse(content);
markdownOutput.innerHTML = html;
// Switch view
placeholder.style.display = 'none';
markdownOutput.style.display = 'block';
// Reset scroll
scrollContainer.scrollTop = 0;
}
function updateFontSize(size) {
markdownOutput.style.fontSize = `${size}px`;
}
function toggleScrolling() {
if (isScrolling) {
stopScrolling();
} else {
startScrolling();
}
}
function startScrolling() {
if (!markdownOutput.innerHTML) return; // Don't scroll if empty
isScrolling = true;
btnPlay.classList.add('active');
iconPlay.style.display = 'none';
iconPause.style.display = 'block';
animationId = requestAnimationFrame(step);
}
function stopScrolling() {
isScrolling = false;
btnPlay.classList.remove('active');
iconPlay.style.display = 'block';
iconPause.style.display = 'none';
cancelAnimationFrame(animationId);
}
function step() {
if (!isScrolling) return;
// Calculate scroll amount based on speed
// Map 1-100 to sensible range.
// Let's say max speed (100) is ~300 pixels per second? ~5px/frame at 60fps
// Min speed (1) is ~10 pixels per second? ~0.16px/frame
// Formula: (speed / 100) * maxPixelsPerFrame
// Adding a base speed so 1 isn't 0.
const maxSpeedValues = 5.0;
const minSpeedValues = 0.2;
// Exponential curve for better control at lower speeds
// Normalized speed 0-1
const normalizedSpeed = (scrollSpeed - 1) / 99;
// Linear interpolation? Or quadratic? Quadratic feels "acceleration"
// Let's stick to linear for predictability first, user says "simple"
const pixelsPerFrame = minSpeedValues + (normalizedSpeed * (maxSpeedValues - minSpeedValues));
scrollAccumulator += pixelsPerFrame;
const pixelsToScroll = Math.floor(scrollAccumulator);
if (pixelsToScroll >= 1) {
// Stop if at bottom
// Use a small buffer (1px) for float inconsistencies
if (Math.ceil(scrollContainer.scrollTop + scrollContainer.clientHeight) >= scrollContainer.scrollHeight) {
stopScrolling();
return;
}
scrollContainer.scrollTop += pixelsToScroll;
scrollAccumulator -= pixelsToScroll;
}
animationId = requestAnimationFrame(step);
}
init();