PageSourceSearch

https://raptorwrite.com/static/pomodoro.js

js raptorwrite.com collected 2026-10-05 07:25:02 UTC 18,323 bytes, 496 lines download raw bytes

1import { PomodoroState, CountdownState } from './state.js';
2import { submitRequest, confirmSuggestion } from './llm.js';
3/**
4 * Formats seconds into a stopwatch display format (MM:SS)
5 * @param {number} seconds - The number of seconds to format
6 * @returns {string} - Formatted time string
7 */
8function stopWatchFormat(seconds) {
9    const minutes = Math.floor(seconds / 60);
10    const remainingSeconds = seconds % 60;
11    return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
12}
13
14/**
15 * Updates the Pomodoro sprint counter display
16 */
17function updatePomodoroSprintCounter() {
18    const sprintCounterElement = document.getElementById('pomodoro-sprint-timer');
19    if (sprintCounterElement) {
20        sprintCounterElement.textContent = stopWatchFormat(PomodoroState.currentTime) || 0;
21    } else {
22        console.warn("Pomodoro sprint counter element not found");
23    }
24}
25
26/**
27 * Updates the Autocontinue countdown display
28 */
29function updateAutocontinueCountdown() {
30    const countdownElement = document.getElementById('countdown-timer');
31    if (countdownElement) {
32        countdownElement.textContent = stopWatchFormat(CountdownState.currentTime) || 0;
33    } else {
34        console.warn("Autocontinue countdown timer element not found");
35    }
36}
37
38/**
39 * Initializes the Pomodoro timer functionality
40 */
41export function initializePomodoro() {
42    const pomodoroToggle = document.getElementById('pomodoro-toggle');
43    
44    // Check if elements exist before proceeding
45    if (!pomodoroToggle) {
46        console.error("Pomodoro toggle element not found");
47        return;
48    }
49    
50    const statusBarStats = document.getElementById('status-bar-stats');
51    const statusBarTime = document.getElementById('status-bar-time');
52    
53    // Add null checks for these elements
54    if (!statusBarStats) {
55        console.warn("Status bar stats element not found, creating it");
56        const newStatsBar = document.createElement('div');
57        newStatsBar.id = 'status-bar-stats';
58        document.querySelector('footer').appendChild(newStatsBar);
59        statusBarStats = newStatsBar;
60    }
61    
62    if (!statusBarTime) {
63        console.warn("Status bar time element not found, creating it");
64        const newTimeBar = document.createElement('div');
65        newTimeBar.id = 'status-bar-time';
66        newTimeBar.classList.add('hidden');
67        newTimeBar.textContent = "00:00";
68        document.querySelector('footer').appendChild(newTimeBar);
69        statusBarTime = newTimeBar;
70    }
71    
72    // Initialize the sprint counter display
73    updatePomodoroSprintCounter();
74    updateAutocontinueCountdown();
75    
76    pomodoroToggle.addEventListener('change', function(e) {
77        // Prevent event from bubbling up to prevent conflicts
78        e.stopPropagation();
79        
80        try {
81            if (this.checked) {
82                if (statusBarStats) statusBarStats.classList.add('hidden');
83                if (statusBarTime) statusBarTime.classList.remove('hidden');
84                
85                const pomodoroOptions = document.querySelectorAll('#start-pomodoro, #pause-pomodoro, #reset-pomodoro, #pomodoro-settings, #autocontinue-toggle, #autocontinue-settings');
86                pomodoroOptions.forEach(option => {
87                    if (option) {
88                        option.classList.remove('disabled');
89                        const link = option.querySelector('a');
90                        if (link) link.classList.remove('text-opacity-50', 'cursor-not-allowed');
91                        
92                        const checkbox = option.querySelector('input[type="checkbox"]');
93                        if (checkbox) checkbox.removeAttribute('disabled');
94                    }
95                });
96            } else {
97                if (statusBarStats) statusBarStats.classList.remove('hidden');
98                if (statusBarTime) statusBarTime.classList.add('hidden');
99                
100                const pomodoroOptions = document.querySelectorAll('#start-pomodoro, #pause-pomodoro, #reset-pomodoro, #pomodoro-settings, #autocontinue-toggle, #autocontinue-settings');
101                pomodoroOptions.forEach(option => {
102                    if (option) {
103                        option.classList.add('disabled');
104                        const link = option.querySelector('a');
105                        if (link) link.classList.add('text-opacity-50', 'curs
105or-not-allowed');
106                        
107                        const checkbox = option.querySelector('input[type="checkbox"]');
108                        if (checkbox) checkbox.setAttribute('disabled', 'disabled');
109                    }
110                });
111            }
112        } catch (error) {
113            console.error("Error in Pomodoro toggle handler:", error);
114        }
115    });
116    
117    // Initialize state without causing an event
118    if (pomodoroToggle && !pomodoroToggle.checked) {
119        try {
120            const pomodoroOptions = document.querySelectorAll('#start-pomodoro, #pause-pomodoro, #reset-pomodoro, #pomodoro-settings, #autocontinue-toggle, #autocontinue-settings');
121            pomodoroOptions.forEach(option => {
122                if (option) {
123                    option.classList.add('disabled');
124                    const link = option.querySelector('a');
125                    if (link) link.classList.add('text-opacity-50', 'cursor-not-allowed');
126                    
127                    const checkbox = option.querySelector('input[type="checkbox"]');
128                    if (checkbox) checkbox.setAttribute('disabled', 'disabled');
129                }
130            });
131        } catch (error) {
132            console.error("Error in Pomodoro initialization:", error);
133        }
134    }
135    
136    // Set up event listeners for Pomodoro control buttons
137    document.getElementById('start-pomodoro')?.addEventListener('click', () => {
138        startPomodoro(PomodoroState.pomodoroDuration);
139    });
140    
141    document.getElementById('pause-pomodoro')?.addEventListener('click', togglePausePomodoro);
142    
143    document.getElementById('reset-pomodoro')?.addEventListener('click', resetPomodoro);
144    
145    // Add event listener for settings button
146    document.getElementById('pomodoro-settings')?.addEventListener('click', openPomodoroSettingsModal);
147    
148    // Add event listener for autocontinue settings button
149    document.getElementById('autocontinue-settings')?.addEventListener('click', openAutocontinueSettingsModal);
150    
151    // Add event listener for autocontinue checkbox to initialize continues when turned on
152    document.getElementById('autocontinue-checkbox')?.addEventListener('change', function(e) {
153        if (this.checked) {
154            // Reset continues to max when turned on
155            CountdownState.continues = CountdownState.maxContinues;
156            // Reset countdown timer
157            CountdownState.currentTime = CountdownState.countdownDuration;
158            updateAutocontinueCountdown();
159        }
160    });
161}
162
163/**
164 * Opens a modal for Pomodoro settings
165 */
166function openPomodoroSettingsModal() {
167    // Create the modal backdrop
168    const modalBackdrop = document.createElement('div');
169    modalBackdrop.id = 'pomodoro-settings-modal-backdrop';
170    modalBackdrop.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center';
171    
172    // Current duration in minutes for display
173    const currentDurationMinutes = Math.floor(PomodoroState.pomodoroDuration / 60);
174    
175    // Create modal content
176    modalBackdrop.innerHTML = `
177        <div id="pomodoro-settings-modal-content" class="bg-white dark:bg-gray-800 rounded-lg p-6 w-96 shadow-xl">
178            <h2 class="text-xl font-bold mb-4 dark:text-white">Pomodoro Settings</h2>
179            <div class="mb-4">
180                <label for="pomodoro-duration" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
181                    Duration (minutes)
182                </label>
183                <input 
184                    type="number" 
185                    id="pomodoro-duration" 
186                    class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
187                    value="${currentDurationMinutes}"
188                    min="1"
189                    max="120"
190                >
191            </div>
192            <div class="flex justify-end space-x-3">
193                <button 
194                    id="pomodoro-settings-save" 
195                    class="btn btn-secondary"
196                >
197                    Save
198                </button>
199                <button 
200                    id="pomodoro-settings-cancel" 
201                    class="btn btn-white"
202                >
203                    Cancel
204                </button>                
205            </div>
206        </div>
207    `;
208    
209    // Add modal to the document
210    document.body.appendChild(modalBackdrop);
211    
212    // Focus on input
213    setTimeout(() => {
214        document.getElementById('pomodoro-duration')?.focus();
215    }, 100);
216    
217    // Add event listeners
218    document.getElementById('pomodoro-settings-cancel')?.addEventListener('click', closePomodoroSettingsModal);
219    document.getElementById('pomodoro-settings-save')?.addEventListener('click', savePomodoroSettings);
220    
221    // Improved backdrop click handler that checks if the click was directly on the backdrop
222    // We'll use mousedown/mouseup pattern instead of click to prevent issues with selection
223    let isMouseDownOnBackdrop = false;
224    
225    modalBackdrop.addEventListener('mousedown', (e) => {
226        // Only set the flag if the click is directly on the backdrop (not on any children)
227        if (e.target === modalBackdrop) {
228            isMouseDownOnBackdrop = true;
229        }
230    });
231    
232    modalBackdrop.addEventListener('mouseup', (e) => {
233        // Only close if both mousedown and mouseup occurred on the backdrop
234        if (e.target === modalBackdrop && isMouseDownOnBackdrop) {
235            closePomodoroSettingsModal();
236        }
237        isMouseDownOnBackdrop = false;
238    });
239    
240    // Reset the flag if mouse leaves the backdrop
241    modalBackdrop.addEventListener('mouseleave', () => {
242        isMouseDownOnBackdrop = false;
243    });
244    
245    // Add keyboard event listener for Enter and Escape
246    document.addEventListener('keydown', handlePomodoroSettingsKeydown);
247}
248
249/**
250 * Handles keyboard events for the Pomodoro settings modal
251 * @param {KeyboardEvent} e - The keyboard event
252 */
253function handlePomodoroSettingsKeydown(e) {
254    // Only handle events when the modal is open
255    const modal = document.getElementById('pomodoro-settings-modal-backdrop');
256    if (!modal) return;
257    
258    if (e.key === 'Enter') {
259        savePomodoroSettings();
260    } else if (e.key === 'Escape') {
261        closePomodoroSettingsModal();
262    }
263}
264
265/**
266 * Closes the Pomodoro settings modal
267 */
268function closePomodoroSettingsModal() {
269    const modal = document.getElementById('pomodoro-settings-modal-backdrop');
270    if (modal) {
271        // Remove event listener
272        document.removeEventListener('keydown', handlePomodoroSettingsKeydown);
273        // Remove modal
274        modal.remove();
275    }
276}
277
278/**
279 * Saves the Pomodoro settings and updates the timer
280 */
281function savePomodoroSettings() {
282    const durationInput = document.getElementById('pomodoro-duration');
283    if (durationInput) {
284        let newDuration = parseInt(durationInput.value, 10);
285        
286        // Validate input
287        if (isNaN(newDuration) || newDuration < 1) {
288            newDuration = 25; // Default to 25 minutes if invalid
289        } else if (newDuration > 120) {
290            newDuration = 120; // Cap at 120 minutes
291        }
292        
293        // Convert to seconds and save to state
294        PomodoroState.pomodoroDuration = newDuration * 60;
295        
296        // If timer is not running, update the current time to match the new duration
297        if (!PomodoroState.isRunning) {
298            PomodoroState.currentTime = PomodoroState.pomodoroDuration;
299            updatePomodoroSprintCounter();
300        }
301        
302        // Close the modal
303        closePomodoroSettingsModal();
304    }
305}
306
307/**
308 * Placeholder function for running auto-continue functionality
309 */
310function runAutoContinue() {
311    // Implementation will be added later
312    if (CountdownState.continues > 0) {
313        CountdownState.continues--;
314        // Reset the time on the Countdown
315        if (CountdownState.continues > 0) {
316            CountdownState.currentTime = CountdownState.countdownDuration;
317        }
318        
319        updateAutocontinueCountdown();
320        
321        // Call the submitRequest function to trigger LLM run
322        confirmSuggestion();
323        submitRequest();
324        
325        console.log("Running auto-continue");
326    } else {
327        const autocontinueCheckbox = document.getElementById('autocontinue-checkbox');
328        if (autocontinueCheckbox) {
329            autocontinueCheckbox.checked = false;
330        }
331    }
332}
333
334
335/**
336 * Starts a Pomodoro timer with the specified duration
337 * @param {number} duration - The duration in seconds
338 */
339export function startPomodoro(duration) {
340   
341    if (PomodoroState.isRunning) return; // Prevent multiple timers
342
343    PomodoroState.isRunning = true;
344    PomodoroState.currentTime = duration;
345    
346    // When starting a pomodoro, also initialize the countdown timer
347    const autocontinueToggle = document.getElementById('autocontinue-checkbox');
348    if (autocontinueToggle && autocontinueToggle.checked) {
349        // Reset countdown to its full duration when starting a new pomodoro
350        CountdownState.currentTime = CountdownState.countdownDuration;
351        CountdownState.continues = CountdownState.maxContinues;
352        updateAutocontinueCountdown();
353    }
354    
355    PomodoroState.intervalId = setInterval(() => {
356        
357        if (PomodoroState.currentTime > 0) {
358            PomodoroState.currentTime--;
359            updatePomodoroSprintCounter();
360            
361            // Also update the countdown timer if it's enabled
362            if (document.getElementById('autocontinue-checkbox').checked) {
363                if (CountdownState.currentTime > 0) {
364                    CountdownState.currentTime--;
365                    updateAutocontinueCountdown();
366                } else {
367                    
368                    runAutoContinue();
369                }
370            }
371        } else {
372            stopPomodoro();
373            alert("Pomodoro session complete!");
374        }
375    }, 1000);
376}
377
378/**
379 * Stops the Pomodoro timer
380 */
381export function stopPomodoro() {
382    clearInterval(PomodoroState.intervalId);
383    PomodoroState.isRunning = false;
384    PomodoroState.intervalId = null;
385}
386
387/**
388 * Toggles the paused state of the Pomodoro timer
389 */
390export function togglePausePomodoro() {
391    if (PomodoroState.intervalId) {
392        // Using stopPomodoro internally, but we don't expose it directly through a button
393        stopPomodoro();
394    } else if (PomodoroState.currentTime > 0) {
395        startPomodoro(PomodoroState.currentTime);
396    }
397}
398
399/**
400 * Resets the Pomodoro timer to its initial duration
401 */
402export function resetPomodoro() {
403    stopPomodoro();
404    PomodoroState.currentTime = PomodoroState.pomodoroDuration;
405    updatePomodoroSprintCounter();
406    
407    // Also reset the countdown timer if autocontinue is enabled
408    const autocontinueToggle = document.getElementById('autocontinue-toggle');
409    CountdownState.currentTime = CountdownState.countdownDuration;
410    CountdownState.continues = CountdownState.maxContinues;
411    updateAutocontinueCountdown();
412}
413
414/**
415 * Opens the autocontinue settings modal and populates it with current settings
416 */
417function openAutocontinueSettingsModal() {
418    // Get the modal element
419    const modal = document.getElementById('autocontinue-settings-modal');
420    if (!modal) {
421        console.error("Autocontinue settings modal not found");
422        return;
423    }
424    
425    // Load current settings into the form
426    const maxContinuesInput = document.getElementById('max-continues');
427    const durationInput = document.getElementById('autocontinue-duration');
428    
429    // Set default values from CountdownState (or set defaults if not available)
430    if (maxContinuesInput) {
431        maxContinuesInput.value = CountdownState.maxContinues || 3;
432    }
433    
434    if (durationInput) {
435        durationInput.value = CountdownState.countdownDuration || 5;
436    }
437    
438    // Show the modal
439    modal.showModal();
440    
441    // Set up save button handler
442    document.getElementById('autocontinue-settings-save')?.addEventListener('click', saveAutocontinueSettings);
443    
444    // Set up cancel button handler
445    document.getElementById('autocontinue-settings-cancel')?.addEventListener('click', () => {
446        modal.close();
447    });
448}
449
450/**
451 * Saves the autocontinue settings
452 */
453function saveAutocontinueSettings() {
454    const maxContinuesInput = document.getElementById('max-continues');
455    const durationInput = document.getElementById('autocontinue-duration');
456    const modal = document.getElementById('autocontinue-settings-modal');
457    
458    // Save values to CountdownState
459    if (maxContinuesInput) {
460        let maxContinues = parseInt(maxContinuesInput.value);
461        if (!isNaN(maxContinues) && maxContinues >= 0) {
462            CountdownState.maxContinues = maxContinues;
463        }
464    }
465    
466    if (durationInput) {
467        let duration = parseInt(durationInput.value);
468        if (!isNaN(duration) && duration > 0) {
469            CountdownState.countdownDuration = duration;
470            
471            // If countdown is not currently running, initialize the current time
472            if (!CountdownState.isRunning) {
473                CountdownState.countdownDuration = duration;
474                CountdownState.currentTime = duration;
475                // Update the display with the new duration
476                console.log("Updating autocontinue countdown");
477                updateAutocontinueCountdown();
478            }
479        }
480    }
481    
482    // Reset continues counter
483    CountdownState.continues = 0;
484    
485    // Close the modal
486    if (modal) {
487        modal.close();
488    }
489}
490
491// Export utility functions that might be needed externally
492export { 
493    stopWatchFormat, 
494    updatePomodoroSprintCounter,
495    updateAutocontinueCountdown
496}; 

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.