import { ItemView, WorkspaceLeaf, } from 'obsidian'; import { VIEW_TYPE_FOCUS_TASK, FocusTask } from './types'; import { QuickAddTaskModal, EditTaskModal } from './modals'; import FocusTaskPlugin from './main'; // ============ Main View ============ export class FocusTaskView extends ItemView { plugin: FocusTaskPlugin; currentFilter: string = 'all'; // References to elements that need frequent updates private timerTimeEl: HTMLElement | null = null; private progressBarEl: HTMLElement | null = null; private actualTimeEl: HTMLElement | null = null; private pauseBtnEl: HTMLElement | null = null; constructor(leaf: WorkspaceLeaf, plugin: FocusTaskPlugin) { super(leaf); this.plugin = plugin; } getViewType(): string { return VIEW_TYPE_FOCUS_TASK; } getDisplayText(): string { return 'Focus Task'; } getIcon(): string { return 'zap'; } async onOpen() { this.refresh(); } // Light update - only updates timer display without rebuilding DOM updateTimerDisplay() { if (!this.timerTimeEl) return; // Update timer text this.timerTimeEl.textContent = this.plugin.formatTime(this.plugin.currentTimerSeconds); // Update progress bar if (this.progressBarEl) { let progressPercent = 0; if (this.plugin.isBreakMode) { const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 ? this.plugin.settings.longBreakMinutes * 60 : this.plugin.settings.pomodoroBreakMinutes * 60; progressPercent = ((breakDuration - this.plugin.currentTimerSeconds) / breakDuration) * 100; } else { const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60; progressPercent = ((workDuration - this.plugin.currentTimerSeconds) / workDuration) * 100; } this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; } // Update actual time display if (this.actualTimeEl && this.plugin.activeTaskId) { const task = this.plugin.data.tasks.find(t => t.id === this.plugin.activeTaskId); if (task) { this.actualTimeEl.textContent = `Actual: ${this.plugin.formatTimeHuman(task.actualMinutes)}`; } } } refresh() { const container = this.containerEl.children[1]; container.empty(); container.addClass('focus-task-container'); // Reset element references this.timerTimeEl = null; this.progressBarEl = null; this.actualTimeEl = null; this.pauseBtnEl = null; // Header this.renderHeader(container); // Stats bar this.renderStatsBar(container); // Active task / Timer this.renderActiveTask(container); // Task list this.renderTaskList(container); } renderHeader(container: Element) { const header = container.createEl('div', { cls: 'focus-task-header' }); const titleSection = header.createEl('div', { cls: 'focus-task-title-section' }); titleSection.createEl('h2', { text: '⚡ Focus Task', cls: 'focus-task-title' }); const today = new Date(); const dateStr = today.toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' }); titleSection.createEl('div', { text: dateStr, cls: 'focus-task-date' }); const actions = header.createEl('div', { cls: 'focus-task-header-actions' }); const addBtn = actions.createEl('button', { cls: 'focus-task-btn focus-task-btn-primary' }); addBtn.innerHTML = '+ Add Task'; addBtn.addEventListener('click', () => { new QuickAddTaskModal(this.app, this.plugin).open(); }); } renderStatsBar(container: Element) { const stats = this.plugin.getStats(); const statsBar = container.createEl('div', { cls: 'focus-task-stats-bar' }); const statItems = [ { label: 'Pending', value: stats.pendingCount.toString(), icon: '📋' }, { label: 'Done Today', value: stats.completedToday.toString(), icon: '✅' }, { label: 'Focus Time', value: this.plugin.formatTimeHuman(stats.totalFocusMinutesToday), icon: '⏱️' }, { label: 'Streak', value: `${stats.streak} days`, icon: '🔥' }, ]; statItems.forEach(stat => { const item = statsBar.createEl('div', { cls: 'focus-task-stat-item' }); item.createEl('div', { cls: 'focus-task-stat-icon', text: stat.icon }); item.createEl('div', { cls: 'focus-task-stat-value', text: stat.value }); item.createEl('div', { cls: 'focus-task-stat-label', text: stat.label }); }); } renderActiveTask(container: Element) { const activeSection = container.createEl('div', { cls: 'focus-task-active-section' }); if (this.plugin.activeTaskId) { const task = this.plugin.data.tasks.find(t => t.id === this.plugin.activeTaskId); if (task) { activeSection.addClass('focus-task-has-active'); const activeCard = activeSection.createEl('div', { cls: 'focus-task-active-card' }); if (this.plugin.isBreakMode) { activeCard.addClass('focus-task-break-card'); const breakLabel = this.plugin.currentTimerSeconds > 0 ? '☕ BREAK TIME' : '✨ BREAK COMPLETE'; activeCard.createEl('div', { cls: 'focus-task-active-label', text: breakLabel }); } else { const workLabel = this.plugin.currentTimerSeconds > 0 ? '🎯 FOCUSING ON' : '🍅 POMODORO COMPLETE'; activeCard.createEl('div', { cls: 'focus-task-active-label', text: workLabel }); } activeCard.createEl('div', { cls: 'focus-task-active-task-name', text: task.text }); // Timer display - store reference for updates const timerDisplay = activeCard.createEl('div', { cls: 'focus-task-timer-display' }); this.timerTimeEl = timerDisplay.createEl('span', { cls: 'focus-task-timer-time', text: this.plugin.formatTime(this.plugin.currentTimerSeconds) }); // Progress bar - store reference for updates const progressWrap = activeCard.createEl('div', { cls: 'focus-task-progress-wrap' }); this.progressBarEl = progressWrap.createEl('div', { cls: 'focus-task-progress-bar' }); let progressPercent = 0; if (this.plugin.isBreakMode) { const breakDuration = this.plugin.pomodoroCount % this.plugin.settings.longBreakInterval === 0 ? this.plugin.settings.longBreakMinutes * 60 : this.plugin.settings.pomodoroBreakMinutes * 60; progressPercent = ((breakDuration - this.plugin.currentTimerSeconds) / breakDuration) * 100; } else { const workDuration = this.plugin.settings.pomodoroWorkMinutes * 60; progressPercent = ((workDuration - this.plugin.currentTimerSeconds) / workDuration) * 100; } this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; if (progressPercent >= 100) this.progressBarEl.addClass('focus-task-overtime'); // Time info - store reference for actual time updates if (!this.plugin.isBreakMode) { const timeInfo = activeCard.createEl('div', { cls: 'focus-task-time-info' }); timeInfo.createEl('span', { text: `Est: ${this.plugin.formatTimeHuman(task.estimatedMinutes)}` }); this.actualTimeEl = timeInfo.createEl('span', { text: `Actual: ${this.plugin.formatTimeHuman(task.actualMinutes)}` }); } // Controls const controls = activeCard.createEl('div', { cls: 'focus-task-active-controls' }); if (this.plugin.isBreakMode) { // Break mode controls if (this.plugin.currentTimerSeconds > 0) { // Break is still counting down this.pauseBtnEl = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-secondary' }); this.pauseBtnEl.innerHTML = this.plugin.isTimerRunning ? '⏸ Pause' : '▶ Resume'; this.pauseBtnEl.addEventListener('click', () => this.plugin.toggleTimer()); const skipBreakBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-primary' }); skipBreakBtn.innerHTML = '⏭ Skip Break'; skipBreakBtn.addEventListener('click', () => { this.plugin.isBreakMode = false; this.plugin.startPomodoro(task.id); }); const stopBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-danger' }); stopBtn.innerHTML = '✕ Stop'; stopBtn.addEventListener('click', () => { this.plugin.isBreakMode = false; this.plugin.stopTimer(); }); } else { // Break timer finished - show resume button const resumeWorkBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-success' }); resumeWorkBtn.innerHTML = '▶ Resume Work'; resumeWorkBtn.addEventListener('click', () => { this.plugin.isBreakMode = false; this.plugin.startPomodoro(task.id); }); const stopBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-danger' }); stopBtn.innerHTML = '✕ Stop'; stopBtn.addEventListener('click', () => { this.plugin.isBreakMode = false; this.plugin.stopTimer(); }); } } else { // Work mode controls if (this.plugin.currentTimerSeconds > 0) { // Work session still running this.pauseBtnEl = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-secondary' }); this.pauseBtnEl.innerHTML = this.plugin.isTimerRunning ? '⏸ Pause' : '▶ Resume'; this.pauseBtnEl.addEventListener('click', () => this.plugin.toggleTimer()); const completeBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-success' }); completeBtn.innerHTML = '✓ Complete'; completeBtn.addEventListener('click', () => this.plugin.completeTask(task.id)); const stopBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-danger' }); stopBtn.innerHTML = '✕ Stop'; stopBtn.addEventListener('click', () => this.plugin.stopTimer()); } else { // Work session finished - show break and completion options const startBreakBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-secondary' }); startBreakBtn.innerHTML = '☕ Start Break'; startBreakBtn.addEventListener('click', () => this.plugin.startBreak()); const continueBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-primary' }); continueBtn.innerHTML = '▶ Continue Working'; continueBtn.addEventListener('click', () => this.plugin.startPomodoro(task.id)); const completeBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-success' }); completeBtn.innerHTML = '✓ Complete'; completeBtn.addEventListener('click', () => this.plugin.completeTask(task.id)); const stopBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-danger' }); stopBtn.innerHTML = '✕ Stop'; stopBtn.addEventListener('click', () => this.plugin.stopTimer()); } } } } else { // No active task - show start focus prompt const startPrompt = activeSection.createEl('div', { cls: 'focus-task-start-prompt' }); startPrompt.createEl('div', { cls: 'focus-task-prompt-icon', text: '⚡' }); startPrompt.createEl('div', { cls: 'focus-task-prompt-text', text: 'Ready to focus?' }); startPrompt.createEl('div', { cls: 'focus-task-prompt-hint', text: 'Click ▶ on a task to start a Pomodoro session' }); } } renderTaskList(container: Element) { const listSection = container.createEl('div', { cls: 'focus-task-list-section' }); // Filters const filters = listSection.createEl('div', { cls: 'focus-task-filters' }); const filterOptions = [ { id: 'all', label: 'All Tasks' }, { id: 'today', label: 'Today' }, { id: 'completed', label: 'Completed' }, ...this.plugin.settings.lists.map(l => ({ id: l.id, label: `${l.icon} ${l.name}` })), ]; filterOptions.forEach(opt => { const btn = filters.createEl('button', { cls: `focus-task-filter-btn ${this.currentFilter === opt.id ? 'active' : ''}`, text: opt.label, }); btn.addEventListener('click', () => { this.currentFilter = opt.id; this.refresh(); }); }); // Task items const taskList = listSection.createEl('div', { cls: 'focus-task-task-list' }); let tasks = this.plugin.data.tasks; // Filter tasks if (this.currentFilter === 'today') { tasks = this.plugin.getTodaysTasks(); } else if (this.currentFilter === 'completed') { tasks = this.plugin.data.tasks.filter(t => t.completed); } else if (this.currentFilter !== 'all') { tasks = this.plugin.getTasksByList(this.currentFilter); } // Sort: incomplete first, then by creation date tasks = [...tasks].sort((a, b) => { if (a.completed !== b.completed) return a.completed ? 1 : -1; return b.createdAt - a.createdAt; }); if (tasks.length === 0) { const emptyState = taskList.createEl('div', { cls: 'focus-task-empty-state' }); emptyState.createEl('div', { cls: 'focus-task-empty-icon', text: '📝' }); emptyState.createEl('div', { cls: 'focus-task-empty-text', text: 'No tasks yet' }); emptyState.createEl('div', { cls: 'focus-task-empty-hint', text: 'Add a task to get started!' }); } else { tasks.forEach(task => this.renderTaskItem(taskList, task)); } } renderTaskItem(container: Element, task: FocusTask) { const list = this.plugin.settings.lists.find(l => l.id === task.list); const taskEl = container.createEl('div', { cls: `focus-task-task-item ${task.completed ? 'completed' : ''} ${task.isActive ? 'active' : ''}` }); // Checkbox const checkbox = taskEl.createEl('div', { cls: 'focus-task-checkbox' }); checkbox.innerHTML = task.completed ? '✓' : ''; checkbox.style.borderColor = list?.color || '#6366f1'; if (task.completed) { checkbox.style.backgroundColor = list?.color || '#6366f1'; checkbox.style.color = 'white'; } checkbox.addEventListener('click', (e) => { e.stopPropagation(); if (!task.completed) { this.plugin.completeTask(task.id); } }); // Task content const content = taskEl.createEl('div', { cls: 'focus-task-task-content' }); const taskHeader = content.createEl('div', { cls: 'focus-task-task-header' }); taskHeader.createEl('span', { cls: 'focus-task-task-text', text: task.text }); if (list) { const listBadge = taskHeader.createEl('span', { cls: 'focus-task-list-badge', text: `${list.icon} ${list.name}`, }); listBadge.style.backgroundColor = list.color + '20'; listBadge.style.color = list.color; } const taskMeta = content.createEl('div', { cls: 'focus-task-task-meta' }); taskMeta.createEl('span', { text: `Est: ${this.plugin.formatTimeHuman(task.estimatedMinutes)}` }); if (task.actualMinutes > 0) { const actualSpan = taskMeta.createEl('span'); actualSpan.setText(`Actual: ${this.plugin.formatTimeHuman(task.actualMinutes)}`); if (task.actualMinutes > task.estimatedMinutes) { actualSpan.addClass('focus-task-overtime-text'); } } // Actions const actions = taskEl.createEl('div', { cls: 'focus-task-task-actions' }); if (!task.completed) { // Start pomodoro button const startBtn = actions.createEl('button', { cls: 'focus-task-task-btn', attr: { 'aria-label': 'Start Pomodoro' } }); startBtn.innerHTML = '▶'; startBtn.addEventListener('click', (e) => { e.stopPropagation(); this.plugin.startPomodoro(task.id); }); // Stopwatch mode button const stopwatchBtn = actions.createEl('button', { cls: 'focus-task-task-btn', attr: { 'aria-label': 'Start Stopwatch' } }); stopwatchBtn.innerHTML = '⏱'; stopwatchBtn.addEventListener('click', (e) => { e.stopPropagation(); this.plugin.startTimer(task.id); }); } // Edit button const editBtn = actions.createEl('button', { cls: 'focus-task-task-btn', attr: { 'aria-label': 'Edit' } }); editBtn.innerHTML = '✏️'; editBtn.addEventListener('click', (e) => { e.stopPropagation(); new EditTaskModal(this.app, this.plugin, task).open(); }); // Delete button const deleteBtn = actions.createEl('button', { cls: 'focus-task-task-btn focus-task-delete-btn', attr: { 'aria-label': 'Delete' } }); deleteBtn.innerHTML = '🗑'; deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); this.plugin.deleteTask(task.id); }); } }