147 lines
4.2 KiB
JavaScript
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();
|