Files
David Fencl 188c7d4bc6
All checks were successful
continuous-integration/drone/push Build is passing
chore(frontend): add time braker
2025-11-26 21:13:10 +01:00

170 lines
4.5 KiB
TypeScript

/**
* Work Break Timer Logic
*/
// Constants
const DEFAULT_TIME_MINUTES = 22;
const MAX_TIME_MINUTES = 180;
const SECONDS_PER_MINUTE = 60;
// State
let timeLeft = DEFAULT_TIME_MINUTES * SECONDS_PER_MINUTE; // in seconds
let isRunning = false;
let intervalId: number | null = null;
let audioContext: AudioContext | null = null;
// DOM Elements
const timerDisplay = document.getElementById('timerDisplay') as HTMLElement;
const startBtn = document.getElementById('startBtn') as HTMLButtonElement;
const resetBtn = document.getElementById('resetBtn') as HTMLButtonElement;
const adjustButtons = document.querySelectorAll('.adjust-btn');
/**
* Formats seconds into MM:SS string
*/
function formatTime(seconds: number): string {
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
}
/**
* Updates the timer display
*/
function updateDisplay(): void {
timerDisplay.textContent = formatTime(timeLeft);
// Update document title
document.title = `${formatTime(timeLeft)} - Work Timer`;
}
/**
* Plays a simple beep sound using AudioContext
*/
function playAlarm(): void {
if (!audioContext) {
audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
}
// Create oscillator
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.type = 'sine';
oscillator.frequency.setValueAtTime(880, audioContext.currentTime); // A5
oscillator.frequency.exponentialRampToValueAtTime(440, audioContext.currentTime + 0.5); // Drop to A4
gainNode.gain.setValueAtTime(0.5, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.start();
oscillator.stop(audioContext.currentTime + 0.5);
}
/**
* Handles the timer tick
*/
function onTick(): void {
if (timeLeft > 0) {
timeLeft--;
updateDisplay();
} else {
// Time is up
pauseTimer();
timerDisplay.classList.add('time-up');
playAlarm();
// Play alarm 3 times
setTimeout(playAlarm, 1000);
setTimeout(playAlarm, 2000);
}
}
/**
* Starts or resumes the timer
*/
function startTimer(): void {
if (timeLeft === 0) {
// If time is 0, reset to default before starting?
// Or just do nothing? Requirement says: "Start from currently set time".
// If currently set is 0, we can't really start.
// Let's assume if 0, we reset to default for convenience, or just return.
// Let's just return to be safe, or maybe the user wants to add time first.
if (timeLeft === 0) return;
}
if (!isRunning) {
isRunning = true;
startBtn.textContent = 'Pause';
timerDisplay.classList.remove('time-up');
// Use window.setInterval to avoid TypeScript confusion with NodeJS.Timeout
intervalId = window.setInterval(onTick, 1000);
} else {
// Pause
pauseTimer();
}
}
/**
* Pauses the timer
*/
function pauseTimer(): void {
isRunning = false;
startBtn.textContent = 'Resume';
if (intervalId !== null) {
clearInterval(intervalId);
intervalId = null;
}
}
/**
* Resets the timer
*/
function resetTimer(): void {
pauseTimer();
timeLeft = DEFAULT_TIME_MINUTES * SECONDS_PER_MINUTE;
startBtn.textContent = 'Start';
timerDisplay.classList.remove('time-up');
updateDisplay();
document.title = 'Work Break Timer';
}
/**
* Adjusts the time by adding minutes
*/
function adjustTime(minutes: number): void {
const newTime = timeLeft + (minutes * SECONDS_PER_MINUTE);
const maxSeconds = MAX_TIME_MINUTES * SECONDS_PER_MINUTE;
if (newTime <= maxSeconds) {
timeLeft = newTime;
updateDisplay();
// If timer finished and we add time, remove the alarm style
if (timeLeft > 0) {
timerDisplay.classList.remove('time-up');
}
} else {
// Cap at max
timeLeft = maxSeconds;
updateDisplay();
}
}
// Event Listeners
startBtn.addEventListener('click', startTimer);
resetBtn.addEventListener('click', resetTimer);
adjustButtons.forEach(btn => {
btn.addEventListener('click', () => {
const minutes = parseInt(btn.getAttribute('data-minutes') || '0', 10);
adjustTime(minutes);
});
});
// Initialize
updateDisplay();