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.