diff --git a/main.js b/main.js index 5683bd8..9429f66 100644 --- a/main.js +++ b/main.js @@ -4356,6 +4356,12 @@ var PomodoroUIManager = class { this.pomodoroQuickLongInput = null; this.pomodoroQuickSessionsInput = null; this.pomodoroCalculationResultEl = null; + // New estimation and cycle tracking elements (moved to calculation panel) + this.pomodoroCycleProgressEl = null; + // User override input elements + this.pomodoroUserOverrideHoursInput = null; + this.pomodoroUserOverrideMinutesInput = null; + this.pomodoroUserAddToEstimationCheckbox = null; // private isPomodoroSectionOpen: boolean = false; // No longer needed, section is always "open" this.areButtonsVisible = true; // For Play/Pause/Skip buttons @@ -4432,7 +4438,7 @@ var PomodoroUIManager = class { * @param container The parent element to render into (this.pomodoroRootEl). */ renderPomodoroTimer(container) { - var _a, _b, _c, _d, _e; + var _a, _b, _c, _d, _e, _f; let mainControlsRow = container.querySelector(".pomodoro-main-controls"); if (!mainControlsRow) { mainControlsRow = container.createDiv("pomodoro-main-controls"); @@ -4533,12 +4539,16 @@ var PomodoroUIManager = class { (0, import_obsidian12.setIcon)(this.pomodoroSkipBtn, "skip-forward"); this.pomodoroSkipBtn.addEventListener("click", () => this.plugin.pomodoroService.skipSession()); } + if (!this.pomodoroCycleProgressEl || this.pomodoroCycleProgressEl.parentElement !== container) { + (_e = this.pomodoroCycleProgressEl) == null ? void 0 : _e.remove(); + this.pomodoroCycleProgressEl = container.createDiv("pomodoro-cycle-progress"); + } let settingsPanelContainer = container.querySelector(".pomodoro-settings-panel-container"); if (!settingsPanelContainer) { settingsPanelContainer = container.createDiv("pomodoro-settings-panel-container"); } if (!this.pomodoroQuickSettingsPanelEl || this.pomodoroQuickSettingsPanelEl.parentElement !== settingsPanelContainer) { - (_e = this.pomodoroQuickSettingsPanelEl) == null ? void 0 : _e.remove(); + (_f = this.pomodoroQuickSettingsPanelEl) == null ? void 0 : _f.remove(); this.pomodoroQuickSettingsPanelEl = settingsPanelContainer.createDiv("pomodoro-quick-settings-panel"); this.pomodoroQuickSettingsPanelEl.style.display = "none"; const createQuickSetting = (labelText, inputType = "number") => { @@ -4553,6 +4563,26 @@ var PomodoroUIManager = class { this.pomodoroQuickLongInput = createQuickSetting("Long Break (min):"); this.pomodoroQuickSessionsInput = createQuickSetting("Sessions/Long Break:"); const buttonsContainer = this.pomodoroQuickSettingsPanelEl.createDiv({ cls: "pomodoro-quick-settings-buttons" }); + const overrideContainer = this.pomodoroQuickSettingsPanelEl.createDiv("pomodoro-override-container"); + const overrideLabel = overrideContainer.createEl("label", { text: "Override time (optional):", cls: "pomodoro-override-label" }); + const overrideInputsContainer = overrideContainer.createDiv("pomodoro-override-inputs"); + this.pomodoroUserOverrideHoursInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-hours" }); + this.pomodoroUserOverrideHoursInput.setAttr("min", "0"); + this.pomodoroUserOverrideHoursInput.setAttr("placeholder", "H"); + this.pomodoroUserOverrideHoursInput.value = String(this.plugin.pluginState.pomodoroUserOverrideHours); + const hoursLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small"); + hoursLabel.setText("h"); + this.pomodoroUserOverrideMinutesInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-minutes" }); + this.pomodoroUserOverrideMinutesInput.setAttr("min", "0"); + this.pomodoroUserOverrideMinutesInput.setAttr("placeholder", "M"); + this.pomodoroUserOverrideMinutesInput.value = String(this.plugin.pluginState.pomodoroUserOverrideMinutes); + const minutesLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small"); + minutesLabel.setText("m"); + const toggleContainer = overrideContainer.createDiv("pomodoro-override-toggle-container"); + this.pomodoroUserAddToEstimationCheckbox = toggleContainer.createEl("input", { type: "checkbox", cls: "pomodoro-add-to-estimation" }); + this.pomodoroUserAddToEstimationCheckbox.checked = this.plugin.pluginState.pomodoroUserAddToEstimation; + const toggleLabel = toggleContainer.createEl("label", { text: "Add to estimated time", cls: "pomodoro-toggle-label" }); + toggleLabel.setAttribute("for", "pomodoro-add-to-estimation"); const calculateBtn = buttonsContainer.createEl("button", { text: "Calculate Reading Time", cls: "pomodoro-quick-calculate-btn" }); calculateBtn.addEventListener("click", async () => { const settingsSaved = this._savePomodoroSettings(); @@ -4571,54 +4601,60 @@ var PomodoroUIManager = class { async calculateAndDisplayPomodoroEstimate() { if (!this.plugin || !this.pomodoroCalculationResultEl) return; + this.saveUserOverrideSettings(); const notesForEstimate = this.plugin.reviewController.getTodayNotes(); - if (notesForEstimate.length === 0) { + const userOverrideHours = this.plugin.pluginState.pomodoroUserOverrideHours || 0; + const userOverrideMinutes = this.plugin.pluginState.pomodoroUserOverrideMinutes || 0; + const userOverrideTimeInMinutes = userOverrideHours * 60 + userOverrideMinutes; + if (notesForEstimate.length === 0 && userOverrideTimeInMinutes === 0) { const activeDate = this.plugin.reviewController.getCurrentReviewDateOverride(); const message = activeDate ? `No notes scheduled for ${new Date(activeDate).toLocaleDateString()} to calculate.` : "No notes currently due to calculate."; this.pomodoroCalculationResultEl.setText(message); this.pomodoroCalculationResultEl.style.display = "block"; return; } - let totalReadingTimeInSeconds = 0; - for (const note of notesForEstimate) { - totalReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path); - } - const totalReadingTimeInMinutes = totalReadingTimeInSeconds / 60; - const settings = this.plugin.settings; - const workDuration = settings.pomodoroWorkDuration; - const shortBreakDuration = settings.pomodoroShortBreakDuration; - const longBreakDuration = settings.pomodoroLongBreakDuration; - const sessionsUntilLongBreak = settings.pomodoroSessionsUntilLongBreak; - let pomodorosNeeded = 0; - let sessionsCompletedInCycle = 0; - let remainingReadingTimeMinutes = totalReadingTimeInMinutes; - let totalBreakTimeInMinutes = 0; - if (totalReadingTimeInMinutes === 0) { - this.pomodoroCalculationResultEl.setText("Estimated reading time is 0 minutes."); + const result = await this.plugin.pomodoroService.calculateEstimationFromNotes(notesForEstimate); + if (!result) { + this.pomodoroCalculationResultEl.setText("Unable to calculate estimation."); this.pomodoroCalculationResultEl.style.display = "block"; return; } - while (remainingReadingTimeMinutes > 0) { - pomodorosNeeded++; - remainingReadingTimeMinutes -= workDuration; - sessionsCompletedInCycle++; - if (remainingReadingTimeMinutes <= 0) - break; - if (sessionsCompletedInCycle >= sessionsUntilLongBreak) { - totalBreakTimeInMinutes += longBreakDuration; - sessionsCompletedInCycle = 0; - } else { - totalBreakTimeInMinutes += shortBreakDuration; - } - } - const totalTimeWithBreaksMinutes = pomodorosNeeded * workDuration + totalBreakTimeInMinutes; + const { totalReadingTimeInSeconds, totalReadingTimeInMinutes, pomodorosNeeded, totalTimeWithBreaksMinutes } = result; + const addToEstimation = this.plugin.pluginState.pomodoroUserAddToEstimation || false; const formattedTotalReadingTime = EstimationUtils.formatTime(totalReadingTimeInSeconds); const formattedTotalTimeWithBreaks = EstimationUtils.formatTime(Math.ceil(totalTimeWithBreaksMinutes * 60)); this.pomodoroCalculationResultEl.empty(); - this.pomodoroCalculationResultEl.createEl("p", { text: `Estimated reading time for ${notesForEstimate.length} note(s) in current view: ${formattedTotalReadingTime}.` }); + if (notesForEstimate.length > 0 && totalReadingTimeInSeconds > 0 && (!userOverrideTimeInMinutes || addToEstimation)) { + let baseReadingTimeInSeconds = 0; + for (const note of notesForEstimate) { + baseReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path); + } + const formattedBaseReadingTime = EstimationUtils.formatTime(baseReadingTimeInSeconds); + this.pomodoroCalculationResultEl.createEl("p", { text: `Base reading time for ${notesForEstimate.length} note(s): ${formattedBaseReadingTime}.` }); + } else if (notesForEstimate.length === 0 && userOverrideTimeInMinutes > 0) { + this.pomodoroCalculationResultEl.createEl("p", { text: `Using override time only (no notes).` }); + } + if (userOverrideTimeInMinutes > 0) { + const overrideText = addToEstimation ? `Added ${userOverrideHours}h ${userOverrideMinutes}m override time.` : `Using ${userOverrideHours}h ${userOverrideMinutes}m override time (replacing estimate).`; + this.pomodoroCalculationResultEl.createEl("p", { text: overrideText, cls: "pomodoro-override-info" }); + } this.pomodoroCalculationResultEl.createEl("p", { text: `Requires ~${pomodorosNeeded} Pomodoro work session(s).` }); this.pomodoroCalculationResultEl.createEl("p", { text: `Total time with breaks: ~${formattedTotalTimeWithBreaks}.` }); this.pomodoroCalculationResultEl.style.display = "block"; + this.updateCycleProgressDisplay(); + } + /** + * Saves user override settings to plugin state + */ + saveUserOverrideSettings() { + var _a, _b, _c; + const hours = parseInt(((_a = this.pomodoroUserOverrideHoursInput) == null ? void 0 : _a.value) || "0"); + const minutes = parseInt(((_b = this.pomodoroUserOverrideMinutesInput) == null ? void 0 : _b.value) || "0"); + const addToEstimation = ((_c = this.pomodoroUserAddToEstimationCheckbox) == null ? void 0 : _c.checked) || false; + this.plugin.pluginState.pomodoroUserOverrideHours = hours; + this.plugin.pluginState.pomodoroUserOverrideMinutes = minutes; + this.plugin.pluginState.pomodoroUserAddToEstimation = addToEstimation; + this.plugin.savePluginData(); } /** * Updates the Pomodoro UI based on the current state from PomodoroService. @@ -4704,6 +4740,46 @@ var PomodoroUIManager = class { if (this.pomodoroCalculationResultEl && ((_a = this.pomodoroQuickSettingsPanelEl) == null ? void 0 : _a.style.display) === "none") { this.pomodoroCalculationResultEl.style.display = "none"; } + this.updateCycleProgressDisplay(); + } + /** + * Updates the cycle progress display based on current state + */ + updateCycleProgressDisplay() { + if (!this.pomodoroCycleProgressEl) + return; + const cycleProgress = this.plugin.pomodoroService.getCycleProgress(); + if (cycleProgress) { + const { current, total, workSessionsRemaining, totalWorkSessions, totalTimeMinutes } = cycleProgress; + const completedSessions = totalWorkSessions - workSessionsRemaining; + const totalHours = Math.floor(totalTimeMinutes / 60); + const totalMinutes = Math.round(totalTimeMinutes % 60); + const timeString = totalHours > 0 ? `${totalHours}H/${totalMinutes}M` : `${totalMinutes}M`; + this.pomodoroCycleProgressEl.setText(`Cycles ${current}/${total} - Sessions ${completedSessions}/${totalWorkSessions} - ${timeString}`); + this.pomodoroCycleProgressEl.style.display = ""; + this.pomodoroCycleProgressEl.addClass("cycle-active"); + } else { + this.pomodoroCycleProgressEl.style.display = "none"; + } + } + /** + * Format time in seconds to a readable string + */ + formatTime(totalSeconds) { + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor(totalSeconds % 3600 / 60); + if (hours > 0) { + return `${hours}h ${minutes}m`; + } else { + return `${minutes}m`; + } + } + /** + * Calculate and display estimation for current notes + */ + async calculateAndDisplayEstimation() { + const notesForEstimate = this.plugin.reviewController.getTodayNotes(); + await this.plugin.pomodoroService.calculateEstimationFromNotes(notesForEstimate); } }; @@ -5174,6 +5250,26 @@ var ListViewRenderer = class { // statsCountEl.setText(`${dueNotesForStats.length} notes - ${EstimationUtils.formatTime(totalTime)}${overdueNotes.length > 0 ? ` (${overdueNotes.length} overdue)` : ''}`); // } async _ensureAndUpdateReviewButtonsSection(container, notesForDisplay, selectedNotes) { + let pomodoroContainer = container.querySelector(".sidebar-pomodoro-section"); + if (!pomodoroContainer) { + pomodoroContainer = container.createDiv("sidebar-pomodoro-section"); + const pomodoroSectionContainerEl = pomodoroContainer.createDiv("sidebar-pomodoro-button-container"); + } + if (this.pomodoroUIManager && pomodoroContainer) { + const pomodoroSectionContainerEl = pomodoroContainer.querySelector(".sidebar-pomodoro-button-container"); + if (pomodoroSectionContainerEl) { + this.pomodoroUIManager.attachAndRender(pomodoroSectionContainerEl); + if (this.plugin.settings.pomodoroEnabled) { + pomodoroContainer.style.display = ""; + this.pomodoroUIManager.showPomodoroSection(true); + this.pomodoroUIManager.updatePomodoroUI(); + this.pomodoroUIManager.calculateAndDisplayEstimation(); + } else { + pomodoroContainer.style.display = "none"; + this.pomodoroUIManager.showPomodoroSection(false); + } + } + } let reviewButtonsContainer = container.querySelector(".review-buttons-container"); if (notesForDisplay.length > 0) { if (!reviewButtonsContainer) { @@ -5187,7 +5283,6 @@ var ListViewRenderer = class { nextNoteBtn.addEventListener("click", () => { this.plugin.reviewController.navigateToNextNote(); }); - reviewButtonsContainer.createDiv("sidebar-pomodoro-button-container"); const reviewCurrentBtn = reviewButtonsContainer.createEl("button", { text: "Review Current Note", title: "Review the currently open note if it's due", cls: "review-all-button" }); reviewCurrentBtn.addEventListener("click", () => { this.plugin.reviewController.reviewCurrentNote(); @@ -5204,16 +5299,6 @@ var ListViewRenderer = class { } } reviewButtonsContainer.style.display = ""; - const pomodoroSectionContainerEl = reviewButtonsContainer.querySelector(".sidebar-pomodoro-button-container"); - if (this.pomodoroUIManager && pomodoroSectionContainerEl) { - this.pomodoroUIManager.attachAndRender(pomodoroSectionContainerEl); - if (this.plugin.settings.pomodoroEnabled) { - this.pomodoroUIManager.showPomodoroSection(true); - this.pomodoroUIManager.updatePomodoroUI(); - } else { - this.pomodoroUIManager.showPomodoroSection(false); - } - } let bulkActionButtons = container.querySelector(".review-bulk-actions"); if (!bulkActionButtons) { bulkActionButtons = container.createDiv("review-bulk-actions"); @@ -6447,7 +6532,16 @@ var DEFAULT_PLUGIN_STATE_DATA = { // Default to work duration pomodoroSessionsCompletedInCycle: 0, pomodoroIsRunning: false, - pomodoroEndTimeMs: null + pomodoroEndTimeMs: null, + // Pomodoro Estimation and Cycle Tracking Defaults + pomodoroEstimatedTotalCycles: null, + pomodoroEstimatedWorkSessions: null, + pomodoroIsEstimationActive: false, + pomodoroUserHasModifiedSettings: false, + // User Override Time Settings Defaults + pomodoroUserOverrideHours: 0, + pomodoroUserOverrideMinutes: 0, + pomodoroUserAddToEstimation: false }; var DEFAULT_APP_DATA = { settings: DEFAULT_SETTINGS, @@ -6501,7 +6595,7 @@ var SpaceforgeSettingTab = class extends import_obsidian17.PluginSettingTab { const actionsContainer = containerEl.createEl("div", { cls: "sf-settings-actions" }); const exportBtn = actionsContainer.createEl("button", { text: "Export all data" }); exportBtn.addEventListener("click", () => { - var _a; + var _a, _b, _c, _d, _e, _f, _g, _h; const pluginStateToExport = { schedules: this.plugin.reviewScheduleService.schedules, history: this.plugin.reviewHistoryService.history, @@ -6515,8 +6609,15 @@ var SpaceforgeSettingTab = class extends import_obsidian17.PluginSettingTab { pomodoroTimeLeftInSeconds: this.plugin.pluginState.pomodoroTimeLeftInSeconds, pomodoroSessionsCompletedInCycle: this.plugin.pluginState.pomodoroSessionsCompletedInCycle, pomodoroIsRunning: this.plugin.pluginState.pomodoroIsRunning, - pomodoroEndTimeMs: (_a = this.plugin.pluginState.pomodoroEndTimeMs) != null ? _a : null + pomodoroEndTimeMs: (_a = this.plugin.pluginState.pomodoroEndTimeMs) != null ? _a : null, // Add the missing field + pomodoroEstimatedTotalCycles: (_b = this.plugin.pluginState.pomodoroEstimatedTotalCycles) != null ? _b : null, + pomodoroEstimatedWorkSessions: (_c = this.plugin.pluginState.pomodoroEstimatedWorkSessions) != null ? _c : null, + pomodoroIsEstimationActive: (_d = this.plugin.pluginState.pomodoroIsEstimationActive) != null ? _d : false, + pomodoroUserHasModifiedSettings: (_e = this.plugin.pluginState.pomodoroUserHasModifiedSettings) != null ? _e : false, + pomodoroUserOverrideHours: (_f = this.plugin.pluginState.pomodoroUserOverrideHours) != null ? _f : 0, + pomodoroUserOverrideMinutes: (_g = this.plugin.pluginState.pomodoroUserOverrideMinutes) != null ? _g : 0, + pomodoroUserAddToEstimation: (_h = this.plugin.pluginState.pomodoroUserAddToEstimation) != null ? _h : false }; const dataToExport = { settings: this.plugin.settings, @@ -9570,10 +9671,21 @@ var PomodoroService = class { return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; } updateDurations(work, short, long, sessions) { + const currentWork = this.settings.pomodoroWorkDuration; + const currentShort = this.settings.pomodoroShortBreakDuration; + const currentLong = this.settings.pomodoroLongBreakDuration; + const currentSessions = this.settings.pomodoroSessionsUntilLongBreak; + const userChangedSettings = work !== currentWork || short !== currentShort || long !== currentLong || sessions !== currentSessions; this.settings.pomodoroWorkDuration = work; this.settings.pomodoroShortBreakDuration = short; this.settings.pomodoroLongBreakDuration = long; this.settings.pomodoroSessionsUntilLongBreak = sessions; + if (userChangedSettings) { + this.state.pomodoroUserHasModifiedSettings = true; + this.state.pomodoroIsEstimationActive = false; + this.state.pomodoroEstimatedTotalCycles = null; + this.state.pomodoroEstimatedWorkSessions = null; + } const activeModesForDurationUpdate = ["work", "shortBreak", "longBreak"]; if (!this.state.pomodoroIsRunning && activeModesForDurationUpdate.includes(this.state.pomodoroCurrentMode)) { this.resetTimeForMode(this.state.pomodoroCurrentMode); @@ -9581,6 +9693,126 @@ var PomodoroService = class { this.plugin.savePluginData(); this.notifyUpdate(); } + /** + * Calculate and set estimation based on reading time + */ + async calculateEstimationFromNotes(notes) { + const userOverrideHours = this.state.pomodoroUserOverrideHours || 0; + const userOverrideMinutes = this.state.pomodoroUserOverrideMinutes || 0; + const userOverrideTimeInMinutes = userOverrideHours * 60 + userOverrideMinutes; + const addToEstimation = this.state.pomodoroUserAddToEstimation || false; + let totalReadingTimeInSeconds = 0; + let totalReadingTimeInMinutes = 0; + if (notes.length > 0) { + for (const note of notes) { + totalReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path); + } + totalReadingTimeInMinutes = totalReadingTimeInSeconds / 60; + } + if (userOverrideTimeInMinutes > 0) { + if (addToEstimation) { + totalReadingTimeInMinutes += userOverrideTimeInMinutes; + } else { + totalReadingTimeInMinutes = userOverrideTimeInMinutes; + totalReadingTimeInSeconds = userOverrideTimeInMinutes * 60; + } + } + if (notes.length === 0 && userOverrideTimeInMinutes === 0) { + this.state.pomodoroIsEstimationActive = false; + this.state.pomodoroEstimatedTotalCycles = null; + this.state.pomodoroEstimatedWorkSessions = null; + this.notifyUpdate(); + return null; + } + const settings = this.settings; + const workDuration = settings.pomodoroWorkDuration; + const shortBreakDuration = settings.pomodoroShortBreakDuration; + const longBreakDuration = settings.pomodoroLongBreakDuration; + const sessionsUntilLongBreak = settings.pomodoroSessionsUntilLongBreak; + if (totalReadingTimeInMinutes === 0 || workDuration === 0) { + this.state.pomodoroIsEstimationActive = false; + this.state.pomodoroEstimatedTotalCycles = null; + this.state.pomodoroEstimatedWorkSessions = null; + this.notifyUpdate(); + return null; + } + let pomodorosNeeded = 0; + let sessionsCompletedInCycle = 0; + let remainingReadingTimeMinutes = totalReadingTimeInMinutes; + let totalBreakTimeInMinutes = 0; + while (remainingReadingTimeMinutes > 0) { + pomodorosNeeded++; + remainingReadingTimeMinutes -= workDuration; + sessionsCompletedInCycle++; + if (remainingReadingTimeMinutes <= 0) + break; + if (sessionsCompletedInCycle >= sessionsUntilLongBreak) { + totalBreakTimeInMinutes += longBreakDuration; + sessionsCompletedInCycle = 0; + } else { + totalBreakTimeInMinutes += shortBreakDuration; + } + } + this.state.pomodoroEstimatedTotalCycles = Math.ceil(pomodorosNeeded / sessionsUntilLongBreak); + this.state.pomodoroEstimatedWorkSessions = pomodorosNeeded; + this.state.pomodoroIsEstimationActive = true; + this.plugin.savePluginData(); + this.notifyUpdate(); + return { + totalReadingTimeInSeconds, + totalReadingTimeInMinutes, + pomodorosNeeded, + totalTimeWithBreaksMinutes: pomodorosNeeded * workDuration + totalBreakTimeInMinutes + }; + } + /** + * Get current cycle progress information + */ + getCycleProgress() { + if (!this.state.pomodoroIsEstimationActive || !this.state.pomodoroEstimatedWorkSessions) { + return null; + } + const currentCycle = Math.floor(this.state.pomodoroSessionsCompletedInCycle / this.settings.pomodoroSessionsUntilLongBreak) + 1; + const totalCycles = this.state.pomodoroEstimatedTotalCycles || 1; + const workSessionsRemaining = Math.max(0, this.state.pomodoroEstimatedWorkSessions - this.state.pomodoroSessionsCompletedInCycle); + const totalWorkSessions = this.state.pomodoroEstimatedWorkSessions; + const workDuration = this.settings.pomodoroWorkDuration; + const shortBreakDuration = this.settings.pomodoroShortBreakDuration; + const longBreakDuration = this.settings.pomodoroLongBreakDuration; + const sessionsUntilLongBreak = this.settings.pomodoroSessionsUntilLongBreak; + let totalBreakTime = 0; + let sessionsInCycle = 0; + for (let i = 0; i < totalWorkSessions; i++) { + sessionsInCycle++; + if (i < totalWorkSessions - 1) { + if (sessionsInCycle >= sessionsUntilLongBreak) { + totalBreakTime += longBreakDuration; + sessionsInCycle = 0; + } else { + totalBreakTime += shortBreakDuration; + } + } + } + const totalTimeMinutes = totalWorkSessions * workDuration + totalBreakTime; + return { + current: currentCycle, + total: totalCycles, + workSessionsRemaining, + totalWorkSessions, + totalTimeMinutes + }; + } + /** + * Reset estimation state (call when user wants to clear estimation) + */ + resetEstimation() { + this.state.pomodoroIsEstimationActive = false; + this.state.pomodoroEstimatedTotalCycles = null; + this.state.pomodoroEstimatedWorkSessions = null; + this.state.pomodoroUserHasModifiedSettings = false; + this.plugin.savePluginData(); + this.notifyUpdate(); + } // --- Internal Logic --- startTimerInterval() { if (this.timerInterval !== null) { @@ -10073,7 +10305,7 @@ var SpaceforgePlugin = class extends import_obsidian26.Plugin { } } async savePluginData() { - var _a, _b; + var _a, _b, _c, _d; try { if (!this.settings || typeof this.settings !== "object") { this.settings = JSON.parse(JSON.stringify(DEFAULT_SETTINGS)); @@ -10097,6 +10329,13 @@ var SpaceforgePlugin = class extends import_obsidian26.Plugin { pomodoroIsRunning: typeof this.pluginState.pomodoroIsRunning === "boolean" ? this.pluginState.pomodoroIsRunning : DEFAULT_PLUGIN_STATE_DATA.pomodoroIsRunning, pomodoroEndTimeMs: (_b = this.pluginState.pomodoroEndTimeMs) != null ? _b : null, // Add the missing field + pomodoroEstimatedTotalCycles: (_c = this.pluginState.pomodoroEstimatedTotalCycles) != null ? _c : DEFAULT_PLUGIN_STATE_DATA.pomodoroEstimatedTotalCycles, + pomodoroEstimatedWorkSessions: (_d = this.pluginState.pomodoroEstimatedWorkSessions) != null ? _d : DEFAULT_PLUGIN_STATE_DATA.pomodoroEstimatedWorkSessions, + pomodoroIsEstimationActive: typeof this.pluginState.pomodoroIsEstimationActive === "boolean" ? this.pluginState.pomodoroIsEstimationActive : DEFAULT_PLUGIN_STATE_DATA.pomodoroIsEstimationActive, + pomodoroUserHasModifiedSettings: typeof this.pluginState.pomodoroUserHasModifiedSettings === "boolean" ? this.pluginState.pomodoroUserHasModifiedSettings : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserHasModifiedSettings, + pomodoroUserOverrideHours: typeof this.pluginState.pomodoroUserOverrideHours === "number" ? this.pluginState.pomodoroUserOverrideHours : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserOverrideHours, + pomodoroUserOverrideMinutes: typeof this.pluginState.pomodoroUserOverrideMinutes === "number" ? this.pluginState.pomodoroUserOverrideMinutes : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserOverrideMinutes, + pomodoroUserAddToEstimation: typeof this.pluginState.pomodoroUserAddToEstimation === "boolean" ? this.pluginState.pomodoroUserAddToEstimation : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserAddToEstimation, version: this.manifest.version }; this.pluginState = currentPluginState; diff --git a/main.ts b/main.ts index a9adb7a..72a255b 100644 --- a/main.ts +++ b/main.ts @@ -464,6 +464,13 @@ export default class SpaceforgePlugin extends Plugin { pomodoroSessionsCompletedInCycle: this.pluginState.pomodoroSessionsCompletedInCycle || DEFAULT_PLUGIN_STATE_DATA.pomodoroSessionsCompletedInCycle, pomodoroIsRunning: typeof this.pluginState.pomodoroIsRunning === 'boolean' ? this.pluginState.pomodoroIsRunning : DEFAULT_PLUGIN_STATE_DATA.pomodoroIsRunning, pomodoroEndTimeMs: this.pluginState.pomodoroEndTimeMs ?? null, // Add the missing field + pomodoroEstimatedTotalCycles: this.pluginState.pomodoroEstimatedTotalCycles ?? DEFAULT_PLUGIN_STATE_DATA.pomodoroEstimatedTotalCycles, + pomodoroEstimatedWorkSessions: this.pluginState.pomodoroEstimatedWorkSessions ?? DEFAULT_PLUGIN_STATE_DATA.pomodoroEstimatedWorkSessions, + pomodoroIsEstimationActive: typeof this.pluginState.pomodoroIsEstimationActive === 'boolean' ? this.pluginState.pomodoroIsEstimationActive : DEFAULT_PLUGIN_STATE_DATA.pomodoroIsEstimationActive, + pomodoroUserHasModifiedSettings: typeof this.pluginState.pomodoroUserHasModifiedSettings === 'boolean' ? this.pluginState.pomodoroUserHasModifiedSettings : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserHasModifiedSettings, + pomodoroUserOverrideHours: typeof this.pluginState.pomodoroUserOverrideHours === 'number' ? this.pluginState.pomodoroUserOverrideHours : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserOverrideHours, + pomodoroUserOverrideMinutes: typeof this.pluginState.pomodoroUserOverrideMinutes === 'number' ? this.pluginState.pomodoroUserOverrideMinutes : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserOverrideMinutes, + pomodoroUserAddToEstimation: typeof this.pluginState.pomodoroUserAddToEstimation === 'boolean' ? this.pluginState.pomodoroUserAddToEstimation : DEFAULT_PLUGIN_STATE_DATA.pomodoroUserAddToEstimation, version: this.manifest.version, }; this.pluginState = currentPluginState; // Update the live pluginState diff --git a/models/plugin-data.ts b/models/plugin-data.ts index f5affcd..f425e54 100644 --- a/models/plugin-data.ts +++ b/models/plugin-data.ts @@ -23,6 +23,17 @@ export interface PluginStateData { pomodoroSessionsCompletedInCycle: number; pomodoroIsRunning: boolean; pomodoroEndTimeMs: number | null; // Timestamp when the current session ends + + // Pomodoro Estimation and Cycle Tracking + pomodoroEstimatedTotalCycles: number | null; // Total cycles calculated from reading time + pomodoroEstimatedWorkSessions: number | null; // Total work sessions calculated + pomodoroIsEstimationActive: boolean; // Whether estimation is currently active (not overridden by user) + pomodoroUserHasModifiedSettings: boolean; // Whether user has manually changed H/M settings + + // User Override Time Settings + pomodoroUserOverrideHours: number; // User-specified hours for override + pomodoroUserOverrideMinutes: number; // User-specified minutes for override + pomodoroUserAddToEstimation: boolean; // Whether to add user time to estimation or replace it } /** @@ -44,6 +55,17 @@ export const DEFAULT_PLUGIN_STATE_DATA: PluginStateData = { pomodoroSessionsCompletedInCycle: 0, pomodoroIsRunning: false, pomodoroEndTimeMs: null, + + // Pomodoro Estimation and Cycle Tracking Defaults + pomodoroEstimatedTotalCycles: null, + pomodoroEstimatedWorkSessions: null, + pomodoroIsEstimationActive: false, + pomodoroUserHasModifiedSettings: false, + + // User Override Time Settings Defaults + pomodoroUserOverrideHours: 0, + pomodoroUserOverrideMinutes: 0, + pomodoroUserAddToEstimation: false, }; /** diff --git a/note.md b/note.md new file mode 100644 index 0000000..f8fadfe --- /dev/null +++ b/note.md @@ -0,0 +1,3 @@ +bug postpone all postpones all notes not just one note. from that specific days on entry. +change due date red color to something more subtle + diff --git a/services/pomodoro-service.ts b/services/pomodoro-service.ts index c901afa..34b02fe 100644 --- a/services/pomodoro-service.ts +++ b/services/pomodoro-service.ts @@ -94,11 +94,27 @@ export class PomodoroService { } public updateDurations(work: number, short: number, long: number, sessions: number): void { + // Check if user is modifying from the current settings (indicating manual override) + const currentWork = this.settings.pomodoroWorkDuration; + const currentShort = this.settings.pomodoroShortBreakDuration; + const currentLong = this.settings.pomodoroLongBreakDuration; + const currentSessions = this.settings.pomodoroSessionsUntilLongBreak; + + const userChangedSettings = work !== currentWork || short !== currentShort || long !== currentLong || sessions !== currentSessions; + this.settings.pomodoroWorkDuration = work; this.settings.pomodoroShortBreakDuration = short; this.settings.pomodoroLongBreakDuration = long; this.settings.pomodoroSessionsUntilLongBreak = sessions; + // If user manually changed settings, mark as modified and deactivate estimation + if (userChangedSettings) { + this.state.pomodoroUserHasModifiedSettings = true; + this.state.pomodoroIsEstimationActive = false; + this.state.pomodoroEstimatedTotalCycles = null; + this.state.pomodoroEstimatedWorkSessions = null; + } + // If current session is not running and its mode (which is an active timed mode) had its duration changed, update its timeLeft const activeModesForDurationUpdate: PomodoroMode[] = ['work', 'shortBreak', 'longBreak']; if (!this.state.pomodoroIsRunning && activeModesForDurationUpdate.includes(this.state.pomodoroCurrentMode)) { @@ -108,6 +124,165 @@ export class PomodoroService { this.notifyUpdate(); } + /** + * Calculate and set estimation based on reading time + */ + public async calculateEstimationFromNotes(notes: any[]): Promise<{ + totalReadingTimeInSeconds: number; + totalReadingTimeInMinutes: number; + pomodorosNeeded: number; + totalTimeWithBreaksMinutes: number; + } | null> { + // Apply user override if specified + const userOverrideHours = this.state.pomodoroUserOverrideHours || 0; + const userOverrideMinutes = this.state.pomodoroUserOverrideMinutes || 0; + const userOverrideTimeInMinutes = (userOverrideHours * 60) + userOverrideMinutes; + const addToEstimation = this.state.pomodoroUserAddToEstimation || false; + + let totalReadingTimeInSeconds = 0; + let totalReadingTimeInMinutes = 0; + + // Calculate base reading time from notes + if (notes.length > 0) { + for (const note of notes) { + totalReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path); + } + totalReadingTimeInMinutes = totalReadingTimeInSeconds / 60; + } + + // Apply user override + if (userOverrideTimeInMinutes > 0) { + if (addToEstimation) { + totalReadingTimeInMinutes += userOverrideTimeInMinutes; + } else { + totalReadingTimeInMinutes = userOverrideTimeInMinutes; + totalReadingTimeInSeconds = userOverrideTimeInMinutes * 60; // Update seconds too for consistency + } + } + + // If no notes and no override, return null + if (notes.length === 0 && userOverrideTimeInMinutes === 0) { + this.state.pomodoroIsEstimationActive = false; + this.state.pomodoroEstimatedTotalCycles = null; + this.state.pomodoroEstimatedWorkSessions = null; + this.notifyUpdate(); + return null; + } + + const settings = this.settings; + const workDuration = settings.pomodoroWorkDuration; + const shortBreakDuration = settings.pomodoroShortBreakDuration; + const longBreakDuration = settings.pomodoroLongBreakDuration; + const sessionsUntilLongBreak = settings.pomodoroSessionsUntilLongBreak; + + if (totalReadingTimeInMinutes === 0 || workDuration === 0) { + this.state.pomodoroIsEstimationActive = false; + this.state.pomodoroEstimatedTotalCycles = null; + this.state.pomodoroEstimatedWorkSessions = null; + this.notifyUpdate(); + return null; + } + + let pomodorosNeeded = 0; + let sessionsCompletedInCycle = 0; + let remainingReadingTimeMinutes = totalReadingTimeInMinutes; + let totalBreakTimeInMinutes = 0; + + while (remainingReadingTimeMinutes > 0) { + pomodorosNeeded++; + remainingReadingTimeMinutes -= workDuration; + sessionsCompletedInCycle++; + + if (remainingReadingTimeMinutes <= 0) break; + + if (sessionsCompletedInCycle >= sessionsUntilLongBreak) { + totalBreakTimeInMinutes += longBreakDuration; + sessionsCompletedInCycle = 0; + } else { + totalBreakTimeInMinutes += shortBreakDuration; + } + } + + // Always update estimation when calculate is called (this resets the estimation) + this.state.pomodoroEstimatedTotalCycles = Math.ceil(pomodorosNeeded / sessionsUntilLongBreak); + this.state.pomodoroEstimatedWorkSessions = pomodorosNeeded; + this.state.pomodoroIsEstimationActive = true; + + this.plugin.savePluginData(); + this.notifyUpdate(); + + // Return calculation results for UI display + return { + totalReadingTimeInSeconds, + totalReadingTimeInMinutes, + pomodorosNeeded, + totalTimeWithBreaksMinutes: (pomodorosNeeded * workDuration) + totalBreakTimeInMinutes + }; + } + + /** + * Get current cycle progress information + */ + public getCycleProgress(): { + current: number; + total: number; + workSessionsRemaining: number; + totalWorkSessions: number; + totalTimeMinutes: number; + } | null { + if (!this.state.pomodoroIsEstimationActive || !this.state.pomodoroEstimatedWorkSessions) { + return null; + } + + const currentCycle = Math.floor(this.state.pomodoroSessionsCompletedInCycle / this.settings.pomodoroSessionsUntilLongBreak) + 1; + const totalCycles = this.state.pomodoroEstimatedTotalCycles || 1; + const workSessionsRemaining = Math.max(0, this.state.pomodoroEstimatedWorkSessions - this.state.pomodoroSessionsCompletedInCycle); + const totalWorkSessions = this.state.pomodoroEstimatedWorkSessions; + + // Calculate total time including breaks + const workDuration = this.settings.pomodoroWorkDuration; + const shortBreakDuration = this.settings.pomodoroShortBreakDuration; + const longBreakDuration = this.settings.pomodoroLongBreakDuration; + const sessionsUntilLongBreak = this.settings.pomodoroSessionsUntilLongBreak; + + let totalBreakTime = 0; + let sessionsInCycle = 0; + + for (let i = 0; i < totalWorkSessions; i++) { + sessionsInCycle++; + if (i < totalWorkSessions - 1) { // Don't add break after last session + if (sessionsInCycle >= sessionsUntilLongBreak) { + totalBreakTime += longBreakDuration; + sessionsInCycle = 0; + } else { + totalBreakTime += shortBreakDuration; + } + } + } + + const totalTimeMinutes = (totalWorkSessions * workDuration) + totalBreakTime; + + return { + current: currentCycle, + total: totalCycles, + workSessionsRemaining: workSessionsRemaining, + totalWorkSessions: totalWorkSessions, + totalTimeMinutes: totalTimeMinutes + }; + } + + /** + * Reset estimation state (call when user wants to clear estimation) + */ + public resetEstimation(): void { + this.state.pomodoroIsEstimationActive = false; + this.state.pomodoroEstimatedTotalCycles = null; + this.state.pomodoroEstimatedWorkSessions = null; + this.state.pomodoroUserHasModifiedSettings = false; + this.plugin.savePluginData(); + this.notifyUpdate(); + } + // --- Internal Logic --- diff --git a/styles.css b/styles.css index 7d469f4..ecf6c44 100644 --- a/styles.css +++ b/styles.css @@ -2802,6 +2802,16 @@ button.disabled:hover { .pomodoro-visibility-toggle-container { display: none !important; } +.sidebar-pomodoro-section { + width: 100%; + margin-top: 8px; + margin-bottom: 8px; + padding: 8px; + background-color: var(--background-secondary); + border-radius: var(--radius-m); + border: 1px solid var(--background-modifier-border); + box-sizing: border-box; +} .sidebar-pomodoro-section-container { width: 100%; margin-top: 8px; @@ -2983,14 +2993,99 @@ button.disabled:hover { .pomodoro-calculation-result { font-size: 0.9em; margin-top: 8px; - padding: 8px; + padding: 10px; background-color: var(--background-secondary-alt); border-radius: var(--radius-s); border: 1px solid var(--background-modifier-border); + line-height: 1.4; } .pomodoro-calculation-result p { margin: 4px 0; } +.pomodoro-cycle-progress { + font-size: 0.75em; + color: var(--text-muted); + text-align: center; + margin-top: 4px; + padding: 2px 6px; + border-radius: var(--radius-s); + transition: var(--sf-transition); + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.pomodoro-cycle-progress.cycle-active { + color: var(--sf-info); + background-color: var(--background-modifier-hover); + font-weight: 500; +} +.pomodoro-override-container { + margin: 12px 0; + padding: 10px; + background-color: var(--background-secondary-alt); + border-radius: var(--radius-s); + border: 1px solid var(--background-modifier-border); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} +.pomodoro-override-label { + font-size: 0.9em; + color: var(--text-normal); + font-weight: 500; + margin-bottom: 6px; + display: block; +} +.pomodoro-override-inputs { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 8px; +} +.pomodoro-override-hours, +.pomodoro-override-minutes { + width: 50px; + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: var(--radius-s); + padding: 4px 6px; + text-align: center; + font-size: 0.9em; +} +.pomodoro-override-hours:focus, +.pomodoro-override-minutes:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2); +} +.pomodoro-override-label-small { + font-size: 0.8em; + color: var(--text-muted); + font-weight: 400; +} +.pomodoro-override-toggle-container { + display: flex; + align-items: center; + gap: 8px; +} +.pomodoro-add-to-estimation { + margin: 0; +} +.pomodoro-toggle-label { + font-size: 0.85em; + color: var(--text-normal); + cursor: pointer; + user-select: none; +} +.pomodoro-override-info { + color: var(--sf-info); + font-style: italic; + font-size: 0.9em; + margin-top: 4px; + padding: 4px 6px; + background-color: var(--background-primary); + border-radius: var(--radius-s); + border-left: 3px solid var(--sf-info); +} /* styles/responsive.css */ @media (max-width: 768px) { diff --git a/styles/pomodoro.css b/styles/pomodoro.css index 9f5b7e7..d31ede5 100644 --- a/styles/pomodoro.css +++ b/styles/pomodoro.css @@ -3,6 +3,17 @@ display: none !important; /* Hide the old toggle button */ } +.sidebar-pomodoro-section { + width: 100%; /* Make it fit the width of its parent in the sidebar */ + margin-top: 8px; /* Space above Pomodoro section */ + margin-bottom: 8px; /* Space below Pomodoro section */ + padding: 8px; /* Padding inside the Pomodoro container */ + background-color: var(--background-secondary); /* Match sidebar section bg */ + border-radius: var(--radius-m); /* Consistent border radius */ + border: 1px solid var(--background-modifier-border); /* Consistent border */ + box-sizing: border-box; /* Ensure padding and border are within width */ +} + .sidebar-pomodoro-section-container { width: 100%; /* Make it fit the width of its parent in the sidebar */ margin-top: 8px; /* Space above Pomodoro section */ @@ -207,11 +218,110 @@ .pomodoro-calculation-result { font-size: 0.9em; margin-top: 8px; /* Increased margin */ - padding: 8px; /* Increased padding */ + padding: 10px; /* Increased padding */ background-color: var(--background-secondary-alt); border-radius: var(--radius-s); border: 1px solid var(--background-modifier-border); /* Add border for definition */ + line-height: 1.4; } .pomodoro-calculation-result p { margin: 4px 0; /* Spacing for paragraphs within result */ } + +/* Cycle Tracking Styles */ +.pomodoro-cycle-progress { + font-size: 0.75em; + color: var(--text-muted); + text-align: center; + margin-top: 4px; + padding: 2px 6px; + border-radius: var(--radius-s); + transition: var(--sf-transition); + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.pomodoro-cycle-progress.cycle-active { + color: var(--sf-info); + background-color: var(--background-modifier-hover); + font-weight: 500; +} + +/* User Override Styles */ +.pomodoro-override-container { + margin: 12px 0; + padding: 10px; + background-color: var(--background-secondary-alt); + border-radius: var(--radius-s); + border: 1px solid var(--background-modifier-border); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.pomodoro-override-label { + font-size: 0.9em; + color: var(--text-normal); + font-weight: 500; + margin-bottom: 6px; + display: block; +} + +.pomodoro-override-inputs { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 8px; +} + +.pomodoro-override-hours, +.pomodoro-override-minutes { + width: 50px; + background-color: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: var(--radius-s); + padding: 4px 6px; + text-align: center; + font-size: 0.9em; +} + +.pomodoro-override-hours:focus, +.pomodoro-override-minutes:focus { + outline: none; + border-color: var(--interactive-accent); + box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.2); +} + +.pomodoro-override-label-small { + font-size: 0.8em; + color: var(--text-muted); + font-weight: 400; +} + +.pomodoro-override-toggle-container { + display: flex; + align-items: center; + gap: 8px; +} + +.pomodoro-add-to-estimation { + margin: 0; +} + +.pomodoro-toggle-label { + font-size: 0.85em; + color: var(--text-normal); + cursor: pointer; + user-select: none; +} + +.pomodoro-override-info { + color: var(--sf-info); + font-style: italic; + font-size: 0.9em; + margin-top: 4px; + padding: 4px 6px; + background-color: var(--background-primary); + border-radius: var(--radius-s); + border-left: 3px solid var(--sf-info); +} diff --git a/ui/settings-tab.ts b/ui/settings-tab.ts index a5f65ea..e6d0a7f 100644 --- a/ui/settings-tab.ts +++ b/ui/settings-tab.ts @@ -99,6 +99,13 @@ export class SpaceforgeSettingTab extends PluginSettingTab { pomodoroSessionsCompletedInCycle: this.plugin.pluginState.pomodoroSessionsCompletedInCycle, pomodoroIsRunning: this.plugin.pluginState.pomodoroIsRunning, pomodoroEndTimeMs: this.plugin.pluginState.pomodoroEndTimeMs ?? null, // Add the missing field + pomodoroEstimatedTotalCycles: this.plugin.pluginState.pomodoroEstimatedTotalCycles ?? null, + pomodoroEstimatedWorkSessions: this.plugin.pluginState.pomodoroEstimatedWorkSessions ?? null, + pomodoroIsEstimationActive: this.plugin.pluginState.pomodoroIsEstimationActive ?? false, + pomodoroUserHasModifiedSettings: this.plugin.pluginState.pomodoroUserHasModifiedSettings ?? false, + pomodoroUserOverrideHours: this.plugin.pluginState.pomodoroUserOverrideHours ?? 0, + pomodoroUserOverrideMinutes: this.plugin.pluginState.pomodoroUserOverrideMinutes ?? 0, + pomodoroUserAddToEstimation: this.plugin.pluginState.pomodoroUserAddToEstimation ?? false, }; const dataToExport: SpaceforgePluginData = { diff --git a/ui/sidebar/list-view-renderer.ts b/ui/sidebar/list-view-renderer.ts index 58565b6..261b28a 100644 --- a/ui/sidebar/list-view-renderer.ts +++ b/ui/sidebar/list-view-renderer.ts @@ -125,6 +125,33 @@ export class ListViewRenderer { // } private async _ensureAndUpdateReviewButtonsSection(container: HTMLElement, notesForDisplay: ReviewSchedule[], selectedNotes: string[]): Promise { + // --- Pomodoro Section (Always visible when enabled, independent of notes) --- + let pomodoroContainer = container.querySelector(".sidebar-pomodoro-section") as HTMLElement; + if (!pomodoroContainer) { + pomodoroContainer = container.createDiv("sidebar-pomodoro-section"); + const pomodoroSectionContainerEl = pomodoroContainer.createDiv("sidebar-pomodoro-button-container"); + } + + // Update Pomodoro section visibility and content + if (this.pomodoroUIManager && pomodoroContainer) { + const pomodoroSectionContainerEl = pomodoroContainer.querySelector(".sidebar-pomodoro-button-container") as HTMLElement; + if (pomodoroSectionContainerEl) { + this.pomodoroUIManager.attachAndRender(pomodoroSectionContainerEl); + if (this.plugin.settings.pomodoroEnabled) { + pomodoroContainer.style.display = ''; + this.pomodoroUIManager.showPomodoroSection(true); + this.pomodoroUIManager.updatePomodoroUI(); + + // Automatically calculate estimation for current notes + this.pomodoroUIManager.calculateAndDisplayEstimation(); + } else { + pomodoroContainer.style.display = 'none'; + this.pomodoroUIManager.showPomodoroSection(false); + } + } + } + + // --- Review Buttons Section (Only visible when there are notes) --- let reviewButtonsContainer = container.querySelector(".review-buttons-container") as HTMLElement; // Visibility of review buttons should depend on whether there are notes in the current context (notesForDisplay) @@ -138,8 +165,6 @@ export class ListViewRenderer { const nextNoteBtn = navButtonsContainer.createEl("button", { text: "Next", title: "Navigate to Next Note", cls: "review-all-button" }); nextNoteBtn.addEventListener("click", () => { this.plugin.reviewController.navigateToNextNote(); }); - reviewButtonsContainer.createDiv("sidebar-pomodoro-button-container"); // Placeholder for Pomodoro - const reviewCurrentBtn = reviewButtonsContainer.createEl("button", { text: "Review Current Note", title: "Review the currently open note if it's due", cls: "review-all-button" }); reviewCurrentBtn.addEventListener("click", () => { this.plugin.reviewController.reviewCurrentNote(); }); const reviewAllBtn = reviewButtonsContainer.createEl("button", { text: "Review All", title: "Start Reviewing All Due Notes", cls: "review-all-button" }); @@ -151,18 +176,6 @@ export class ListViewRenderer { } } reviewButtonsContainer.style.display = ''; - - // Pomodoro section - const pomodoroSectionContainerEl = reviewButtonsContainer.querySelector(".sidebar-pomodoro-button-container") as HTMLElement; - if (this.pomodoroUIManager && pomodoroSectionContainerEl) { - this.pomodoroUIManager.attachAndRender(pomodoroSectionContainerEl); // This method will be refactored to be non-destructive - if (this.plugin.settings.pomodoroEnabled) { - this.pomodoroUIManager.showPomodoroSection(true); // Controls overall visibility - this.pomodoroUIManager.updatePomodoroUI(); // Updates internal state and button text - } else { - this.pomodoroUIManager.showPomodoroSection(false); - } - } // Bulk action buttons let bulkActionButtons = container.querySelector(".review-bulk-actions") as HTMLElement; diff --git a/ui/sidebar/pomodoro-ui-manager.ts b/ui/sidebar/pomodoro-ui-manager.ts index 59ec909..62ef6b7 100644 --- a/ui/sidebar/pomodoro-ui-manager.ts +++ b/ui/sidebar/pomodoro-ui-manager.ts @@ -21,6 +21,14 @@ export class PomodoroUIManager { private pomodoroQuickLongInput: HTMLInputElement | null = null; private pomodoroQuickSessionsInput: HTMLInputElement | null = null; private pomodoroCalculationResultEl: HTMLElement | null = null; + + // New estimation and cycle tracking elements (moved to calculation panel) + private pomodoroCycleProgressEl: HTMLElement | null = null; + + // User override input elements + private pomodoroUserOverrideHoursInput: HTMLInputElement | null = null; + private pomodoroUserOverrideMinutesInput: HTMLInputElement | null = null; + private pomodoroUserAddToEstimationCheckbox: HTMLInputElement | null = null; // private isPomodoroSectionOpen: boolean = false; // No longer needed, section is always "open" private areButtonsVisible: boolean = true; // For Play/Pause/Skip buttons @@ -237,6 +245,12 @@ export class PomodoroUIManager { this.pomodoroSkipBtn.addEventListener("click", () => this.plugin.pomodoroService.skipSession()); } + // Cycle Progress Display (shows current cycle and remaining sessions) + if (!this.pomodoroCycleProgressEl || this.pomodoroCycleProgressEl.parentElement !== container) { + this.pomodoroCycleProgressEl?.remove(); + this.pomodoroCycleProgressEl = container.createDiv("pomodoro-cycle-progress"); + } + // Quick Settings Toggle Button - REMOVED // if (!this.pomodoroQuickSettingsToggleBtn || this.pomodoroQuickSettingsToggleBtn.parentElement !== mainControlsRow) { // this.pomodoroQuickSettingsToggleBtn?.remove(); @@ -302,6 +316,35 @@ export class PomodoroUIManager { // } // }); + // User override time inputs + const overrideContainer = this.pomodoroQuickSettingsPanelEl.createDiv("pomodoro-override-container"); + const overrideLabel = overrideContainer.createEl("label", { text: "Override time (optional):", cls: "pomodoro-override-label" }); + + const overrideInputsContainer = overrideContainer.createDiv("pomodoro-override-inputs"); + this.pomodoroUserOverrideHoursInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-hours" }); + this.pomodoroUserOverrideHoursInput.setAttr("min", "0"); + this.pomodoroUserOverrideHoursInput.setAttr("placeholder", "H"); + this.pomodoroUserOverrideHoursInput.value = String(this.plugin.pluginState.pomodoroUserOverrideHours); + + const hoursLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small"); + hoursLabel.setText("h"); + + this.pomodoroUserOverrideMinutesInput = overrideInputsContainer.createEl("input", { type: "number", cls: "pomodoro-override-minutes" }); + this.pomodoroUserOverrideMinutesInput.setAttr("min", "0"); + this.pomodoroUserOverrideMinutesInput.setAttr("placeholder", "M"); + this.pomodoroUserOverrideMinutesInput.value = String(this.plugin.pluginState.pomodoroUserOverrideMinutes); + + const minutesLabel = overrideInputsContainer.createSpan("pomodoro-override-label-small"); + minutesLabel.setText("m"); + + // Add to estimation toggle + const toggleContainer = overrideContainer.createDiv("pomodoro-override-toggle-container"); + this.pomodoroUserAddToEstimationCheckbox = toggleContainer.createEl("input", { type: "checkbox", cls: "pomodoro-add-to-estimation" }); + this.pomodoroUserAddToEstimationCheckbox.checked = this.plugin.pluginState.pomodoroUserAddToEstimation; + + const toggleLabel = toggleContainer.createEl("label", { text: "Add to estimated time", cls: "pomodoro-toggle-label" }); + toggleLabel.setAttribute("for", "pomodoro-add-to-estimation"); + const calculateBtn = buttonsContainer.createEl("button", { text: "Calculate Reading Time", cls: "pomodoro-quick-calculate-btn" }); calculateBtn.addEventListener("click", async () => { const settingsSaved = this._savePomodoroSettings(); @@ -323,10 +366,18 @@ export class PomodoroUIManager { private async calculateAndDisplayPomodoroEstimate(): Promise { if (!this.plugin || !this.pomodoroCalculationResultEl) return; + // Save user override settings + this.saveUserOverrideSettings(); + // Use notes from the review controller, which are context-aware (selected date or actual today) const notesForEstimate = this.plugin.reviewController.getTodayNotes(); - if (notesForEstimate.length === 0) { + // Check if we have either notes or user override + const userOverrideHours = this.plugin.pluginState.pomodoroUserOverrideHours || 0; + const userOverrideMinutes = this.plugin.pluginState.pomodoroUserOverrideMinutes || 0; + const userOverrideTimeInMinutes = (userOverrideHours * 60) + userOverrideMinutes; + + if (notesForEstimate.length === 0 && userOverrideTimeInMinutes === 0) { const activeDate = this.plugin.reviewController.getCurrentReviewDateOverride(); const message = activeDate ? `No notes scheduled for ${new Date(activeDate).toLocaleDateString()} to calculate.` @@ -336,54 +387,68 @@ export class PomodoroUIManager { return; } - let totalReadingTimeInSeconds = 0; - for (const note of notesForEstimate) { - totalReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path); - } - const totalReadingTimeInMinutes = totalReadingTimeInSeconds / 60; - - const settings = this.plugin.settings; - const workDuration = settings.pomodoroWorkDuration; - const shortBreakDuration = settings.pomodoroShortBreakDuration; - const longBreakDuration = settings.pomodoroLongBreakDuration; - const sessionsUntilLongBreak = settings.pomodoroSessionsUntilLongBreak; - - let pomodorosNeeded = 0; - let sessionsCompletedInCycle = 0; - let remainingReadingTimeMinutes = totalReadingTimeInMinutes; - let totalBreakTimeInMinutes = 0; - - if (totalReadingTimeInMinutes === 0) { - this.pomodoroCalculationResultEl.setText("Estimated reading time is 0 minutes."); - this.pomodoroCalculationResultEl.style.display = 'block'; - return; + // Use the PomodoroService to calculate estimation (this also resets and updates cycle tracking) + const result = await this.plugin.pomodoroService.calculateEstimationFromNotes(notesForEstimate); + + if (!result) { + this.pomodoroCalculationResultEl.setText("Unable to calculate estimation."); + this.pomodoroCalculationResultEl.style.display = 'block'; + return; } - while (remainingReadingTimeMinutes > 0) { - pomodorosNeeded++; - remainingReadingTimeMinutes -= workDuration; - sessionsCompletedInCycle++; + const { totalReadingTimeInSeconds, totalReadingTimeInMinutes, pomodorosNeeded, totalTimeWithBreaksMinutes } = result; - if (remainingReadingTimeMinutes <= 0) break; - - if (sessionsCompletedInCycle >= sessionsUntilLongBreak) { - totalBreakTimeInMinutes += longBreakDuration; - sessionsCompletedInCycle = 0; - } else { - totalBreakTimeInMinutes += shortBreakDuration; - } - } - - const totalTimeWithBreaksMinutes = (pomodorosNeeded * workDuration) + totalBreakTimeInMinutes; + // Get user override values for display (already declared above) + const addToEstimation = this.plugin.pluginState.pomodoroUserAddToEstimation || false; const formattedTotalReadingTime = EstimationUtils.formatTime(totalReadingTimeInSeconds); const formattedTotalTimeWithBreaks = EstimationUtils.formatTime(Math.ceil(totalTimeWithBreaksMinutes * 60)); this.pomodoroCalculationResultEl.empty(); - this.pomodoroCalculationResultEl.createEl("p", { text: `Estimated reading time for ${notesForEstimate.length} note(s) in current view: ${formattedTotalReadingTime}.` }); + + // Show base reading time if we have notes and it wasn't completely overridden + if (notesForEstimate.length > 0 && totalReadingTimeInSeconds > 0 && (!userOverrideTimeInMinutes || addToEstimation)) { + // Calculate base reading time without overrides for display + let baseReadingTimeInSeconds = 0; + for (const note of notesForEstimate) { + baseReadingTimeInSeconds += await this.plugin.reviewScheduleService.estimateReviewTime(note.path); + } + const formattedBaseReadingTime = EstimationUtils.formatTime(baseReadingTimeInSeconds); + this.pomodoroCalculationResultEl.createEl("p", { text: `Base reading time for ${notesForEstimate.length} note(s): ${formattedBaseReadingTime}.` }); + } else if (notesForEstimate.length === 0 && userOverrideTimeInMinutes > 0) { + // Show message when using only override time + this.pomodoroCalculationResultEl.createEl("p", { text: `Using override time only (no notes).` }); + } + + // Show user override information if applicable + if (userOverrideTimeInMinutes > 0) { + const overrideText = addToEstimation + ? `Added ${userOverrideHours}h ${userOverrideMinutes}m override time.` + : `Using ${userOverrideHours}h ${userOverrideMinutes}m override time (replacing estimate).`; + this.pomodoroCalculationResultEl.createEl("p", { text: overrideText, cls: "pomodoro-override-info" }); + } + this.pomodoroCalculationResultEl.createEl("p", { text: `Requires ~${pomodorosNeeded} Pomodoro work session(s).` }); this.pomodoroCalculationResultEl.createEl("p", { text: `Total time with breaks: ~${formattedTotalTimeWithBreaks}.` }); this.pomodoroCalculationResultEl.style.display = 'block'; + + // Update the cycle progress display to show the new estimation + this.updateCycleProgressDisplay(); + } + + /** + * Saves user override settings to plugin state + */ + private saveUserOverrideSettings(): void { + const hours = parseInt(this.pomodoroUserOverrideHoursInput?.value || "0"); + const minutes = parseInt(this.pomodoroUserOverrideMinutesInput?.value || "0"); + const addToEstimation = this.pomodoroUserAddToEstimationCheckbox?.checked || false; + + this.plugin.pluginState.pomodoroUserOverrideHours = hours; + this.plugin.pluginState.pomodoroUserOverrideMinutes = minutes; + this.plugin.pluginState.pomodoroUserAddToEstimation = addToEstimation; + + this.plugin.savePluginData(); } /** @@ -487,5 +552,59 @@ export class PomodoroUIManager { if (this.pomodoroCalculationResultEl && this.pomodoroQuickSettingsPanelEl?.style.display === 'none') { this.pomodoroCalculationResultEl.style.display = 'none'; } + + // Update cycle progress display + this.updateCycleProgressDisplay(); } -} + + + + /** + * Updates the cycle progress display based on current state + */ + private updateCycleProgressDisplay(): void { + if (!this.pomodoroCycleProgressEl) return; + + const cycleProgress = this.plugin.pomodoroService.getCycleProgress(); + + if (cycleProgress) { + const { current, total, workSessionsRemaining, totalWorkSessions, totalTimeMinutes } = cycleProgress; + + // Calculate completed sessions + const completedSessions = totalWorkSessions - workSessionsRemaining; + + // Format total time + const totalHours = Math.floor(totalTimeMinutes / 60); + const totalMinutes = Math.round(totalTimeMinutes % 60); + const timeString = totalHours > 0 ? `${totalHours}H/${totalMinutes}M` : `${totalMinutes}M`; + + this.pomodoroCycleProgressEl.setText(`Cycles ${current}/${total} - Sessions ${completedSessions}/${totalWorkSessions} - ${timeString}`); + this.pomodoroCycleProgressEl.style.display = ''; + this.pomodoroCycleProgressEl.addClass('cycle-active'); + } else { + this.pomodoroCycleProgressEl.style.display = 'none'; + } + } + + /** + * Format time in seconds to a readable string + */ + private formatTime(totalSeconds: number): string { + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } else { + return `${minutes}m`; + } + } + + /** + * Calculate and display estimation for current notes + */ + public async calculateAndDisplayEstimation(): Promise { + const notesForEstimate = this.plugin.reviewController.getTodayNotes(); + await this.plugin.pomodoroService.calculateEstimationFromNotes(notesForEstimate); + } +} \ No newline at end of file