From 4303bfa2e63d6130aa2a658d119c9e81a9293bad Mon Sep 17 00:00:00 2001 From: Sayuop Date: Sat, 22 Nov 2025 14:17:36 +0100 Subject: [PATCH] Removed Floating timer -> Status Bar --- README.md | 2 +- manifest.json | 2 +- package.json | 2 +- src/main.ts | 162 ++++++++++++++------------------------------------ src/types.ts | 2 +- src/view.ts | 67 +++++++++++++++++---- styles.css | 90 +++++----------------------- versions.json | 2 +- 8 files changed, 118 insertions(+), 211 deletions(-) diff --git a/README.md b/README.md index b364524..6ecf68d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Focus Task brings the power of time-boxed task management directly into your Obs - **Pomodoro Count**: Track total pomodoros completed ### 🎨 User Experience -- **Floating Timer Widget**: Draggable timer that stays visible while you work +- **Status Bar Timer**: timer that stays visible while you work - **Celebration Messages**: Fun, randomized messages when you complete tasks - **Sound Notifications**: Audio alerts for timer completion and task completion - **Keyboard Shortcuts**: Quick access to common actions diff --git a/manifest.json b/manifest.json index 7fe3bdf..bef1e97 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "focus-task", "name": "Focus Task", - "version": "1.0.0", + "version": "1.0.1", "minAppVersion": "0.15.0", "description": "A Blitzit-inspired task management and focus timer plugin. Plan your day, track time with Pomodoro technique, and crush your tasks with satisfying checkoffs.", "author": "Crib", diff --git a/package.json b/package.json index 0681790..e772400 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "focus-task", - "version": "1.0.0", + "version": "1.0.1", "description": "A Blitzit-inspired task management and focus timer plugin for Obsidian", "main": "main.js", "scripts": { diff --git a/src/main.ts b/src/main.ts index dd7d2b1..c04d9d9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -36,8 +36,8 @@ export default class FocusTaskPlugin extends Plugin { activeTaskId: string | null = null; pomodoroCount: number = 0; - // Floating timer element - floatingTimerEl: HTMLElement | null = null; + // Status Bar element + statusBarEl: HTMLElement | null = null; async onload() { await this.loadAllData(); @@ -91,15 +91,13 @@ export default class FocusTaskPlugin extends Plugin { this.addSettingTab(new FocusTaskSettingTab(this.app, this)); // Create floating timer if enabled - if (this.settings.showFloatingTimer) { - this.createFloatingTimer(); - } + this.createStatusBar(); } onunload() { - this.stopTimer(); - this.removeFloatingTimer(); - } + this.stopTimer(); + } + async loadAllData() { const loaded = await this.loadData(); @@ -272,7 +270,7 @@ export default class FocusTaskPlugin extends Plugin { }, 1000); this.saveAllData(); - this.refreshView(); + this.updateTimerDisplay(); this.updateFloatingTimer(); } @@ -304,7 +302,7 @@ export default class FocusTaskPlugin extends Plugin { }, 1000); this.updateFloatingTimer(); - this.refreshView(); + this.updateTimerDisplay(); } handlePomodoroEnd() { @@ -357,7 +355,7 @@ export default class FocusTaskPlugin extends Plugin { } this.updateFloatingTimer(); - this.refreshView(); + this.updateTimerDisplay(); } toggleTimer() { @@ -388,7 +386,7 @@ export default class FocusTaskPlugin extends Plugin { } this.updateFloatingTimer(); - this.refreshView(); + this.updateTimerDisplay(); } stopTimer() { @@ -419,106 +417,38 @@ export default class FocusTaskPlugin extends Plugin { } } - // ============ Floating Timer ============ + // ============ Status Bar Timer ============ - createFloatingTimer() { - if (this.floatingTimerEl) return; - - this.floatingTimerEl = document.body.createEl('div', { - cls: 'focus-task-floating-timer', - }); - - this.floatingTimerEl.innerHTML = ` -
-
No active task
-
00:00
-
- - -
-
- `; - - // Make draggable - this.makeDraggable(this.floatingTimerEl); - - // Add event listeners - const playPauseBtn = this.floatingTimerEl.querySelector('.play-pause'); - const completeBtn = this.floatingTimerEl.querySelector('.complete'); - - playPauseBtn?.addEventListener('click', () => this.toggleTimer()); - completeBtn?.addEventListener('click', () => this.completeActiveTask()); + createStatusBar() { + this.statusBarEl = this.addStatusBarItem(); + this.statusBarEl.addClass('focus-task-status-bar'); + this.updateStatusBar(); + + // Click to open panel + this.statusBarEl.addEventListener('click', () => { + this.activateView(); + }); } - removeFloatingTimer() { - if (this.floatingTimerEl) { - this.floatingTimerEl.remove(); - this.floatingTimerEl = null; - } - } - - updateFloatingTimer() { - if (!this.floatingTimerEl) return; - - const taskEl = this.floatingTimerEl.querySelector('.focus-task-floating-task'); - const timeEl = this.floatingTimerEl.querySelector('.focus-task-floating-time'); - const playPauseBtn = this.floatingTimerEl.querySelector('.play-pause'); + updateStatusBar() { + if (!this.statusBarEl) return; if (this.activeTaskId) { const task = this.data.tasks.find(t => t.id === this.activeTaskId); - if (task && taskEl) { - taskEl.textContent = this.isBreakMode ? '☕ Break Time' : task.text; - } - } else if (taskEl) { - taskEl.textContent = 'No active task'; - } - - if (timeEl) { - timeEl.textContent = this.formatTime(this.currentTimerSeconds); - timeEl.classList.toggle('focus-task-overtime', this.currentTimerSeconds < 0); - } - - if (playPauseBtn) { - playPauseBtn.textContent = this.isTimerRunning ? '⏸' : '▶'; - } - - // Update color based on state - this.floatingTimerEl.classList.toggle('focus-task-break-mode', this.isBreakMode); - this.floatingTimerEl.classList.toggle('focus-task-active', this.isTimerRunning); - } - - makeDraggable(el: HTMLElement) { - let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; - - el.onmousedown = dragMouseDown; - - function dragMouseDown(e: MouseEvent) { - if ((e.target as HTMLElement).tagName === 'BUTTON') return; - e.preventDefault(); - pos3 = e.clientX; - pos4 = e.clientY; - document.onmouseup = closeDragElement; - document.onmousemove = elementDrag; - } - - function elementDrag(e: MouseEvent) { - e.preventDefault(); - pos1 = pos3 - e.clientX; - pos2 = pos4 - e.clientY; - pos3 = e.clientX; - pos4 = e.clientY; - el.style.top = (el.offsetTop - pos2) + "px"; - el.style.left = (el.offsetLeft - pos1) + "px"; - el.style.right = 'auto'; - el.style.bottom = 'auto'; - } - - function closeDragElement() { - document.onmouseup = null; - document.onmousemove = null; + const taskName = this.isBreakMode ? '☕ Break' : (task?.text.substring(0, 20) || 'Task'); + const timeStr = this.formatTime(this.currentTimerSeconds); + const icon = this.isTimerRunning ? '▶' : '⏸'; + + this.statusBarEl.setText(`⚡ ${icon} ${timeStr} - ${taskName}${task && task.text.length > 20 ? '...' : ''}`); + this.statusBarEl.addClass('focus-task-status-active'); + } else { + this.statusBarEl.setText('⚡ Focus Task'); + this.statusBarEl.removeClass('focus-task-status-active'); } } + + // ============ Sounds & Celebrations ============ showCelebration(task: FocusTask) { @@ -612,6 +542,16 @@ export default class FocusTaskPlugin extends Plugin { }); } + // Light refresh - only updates timer display without rebuilding DOM + updateTimerDisplay() { + const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_FOCUS_TASK); + leaves.forEach(leaf => { + if (leaf.view instanceof FocusTaskView) { + leaf.view.updateTimerDisplay(); + } + }); + } + getTasksByList(listId: string): FocusTask[] { return this.data.tasks.filter(t => t.list === listId); } @@ -761,21 +701,7 @@ class FocusTaskSettingTab extends PluginSettingTab { await this.plugin.saveAllData(); })); - new Setting(containerEl) - .setName('Show Floating Timer') - .setDesc('Display a draggable floating timer widget') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.showFloatingTimer) - .onChange(async value => { - this.plugin.settings.showFloatingTimer = value; - if (value) { - this.plugin.createFloatingTimer(); - } else { - this.plugin.removeFloatingTimer(); - } - await this.plugin.saveAllData(); - })); - + // Lists Management containerEl.createEl('h2', { text: '📋 Lists' }); diff --git a/src/types.ts b/src/types.ts index 6a92e8d..9b6b5ea 100644 --- a/src/types.ts +++ b/src/types.ts @@ -30,7 +30,7 @@ export interface FocusTaskSettings { enableCelebrations: boolean; defaultEstimateMinutes: number; lists: TaskList[]; - showFloatingTimer: boolean; + showFloatingTimer: true; autoStartBreak: boolean; tickSoundEnabled: boolean; } diff --git a/src/view.ts b/src/view.ts index 00278f5..29ef077 100644 --- a/src/view.ts +++ b/src/view.ts @@ -12,6 +12,12 @@ import FocusTaskPlugin from './main'; 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); @@ -34,10 +40,47 @@ export class FocusTaskView extends ItemView { 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); @@ -109,16 +152,16 @@ export class FocusTaskView extends ItemView { activeCard.createEl('div', { cls: 'focus-task-active-task-name', text: task.text }); - // Timer display + // Timer display - store reference for updates const timerDisplay = activeCard.createEl('div', { cls: 'focus-task-timer-display' }); - timerDisplay.createEl('span', { + this.timerTimeEl = timerDisplay.createEl('span', { cls: 'focus-task-timer-time', text: this.plugin.formatTime(this.plugin.currentTimerSeconds) }); - // Progress bar + // Progress bar - store reference for updates const progressWrap = activeCard.createEl('div', { cls: 'focus-task-progress-wrap' }); - const progress = progressWrap.createEl('div', { cls: 'focus-task-progress-bar' }); + this.progressBarEl = progressWrap.createEl('div', { cls: 'focus-task-progress-bar' }); let progressPercent = 0; if (this.plugin.isBreakMode) { @@ -131,22 +174,22 @@ export class FocusTaskView extends ItemView { progressPercent = ((workDuration - this.plugin.currentTimerSeconds) / workDuration) * 100; } - progress.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; - if (progressPercent >= 100) progress.addClass('focus-task-overtime'); + this.progressBarEl.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`; + if (progressPercent >= 100) this.progressBarEl.addClass('focus-task-overtime'); - // Time info + // 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)}` }); - timeInfo.createEl('span', { text: `Actual: ${this.plugin.formatTimeHuman(task.actualMinutes)}` }); + this.actualTimeEl = timeInfo.createEl('span', { text: `Actual: ${this.plugin.formatTimeHuman(task.actualMinutes)}` }); } // Controls const controls = activeCard.createEl('div', { cls: 'focus-task-active-controls' }); - const pauseBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-secondary' }); - pauseBtn.innerHTML = this.plugin.isTimerRunning ? '⏸ Pause' : '▶ Resume'; - pauseBtn.addEventListener('click', () => this.plugin.toggleTimer()); + 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()); if (!this.plugin.isBreakMode) { const completeBtn = controls.createEl('button', { cls: 'focus-task-btn focus-task-btn-success' }); @@ -316,4 +359,4 @@ export class FocusTaskView extends ItemView { this.plugin.deleteTask(task.id); }); } -} +} \ No newline at end of file diff --git a/styles.css b/styles.css index 3785fe6..3c661db 100644 --- a/styles.css +++ b/styles.css @@ -512,86 +512,24 @@ background: var(--ft-danger); } -/* ============ Floating Timer ============ */ -.focus-task-floating-timer { - position: fixed; - bottom: 20px; - right: 20px; - z-index: 9999; - background: var(--background-primary); - border: 1px solid var(--background-modifier-border); - border-radius: 16px; - padding: 12px 16px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); - cursor: move; - min-width: 180px; - transition: all 0.3s ease; +/* ============ Status Bar Timer ============ */ +.focus-task-status-bar { + cursor: pointer; + padding: 0 8px; + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + transition: all 0.2s ease; } -.focus-task-floating-timer:hover { - box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3); +.focus-task-status-bar:hover { + color: var(--text-accent); } -.focus-task-floating-timer.focus-task-active { - border-color: var(--ft-primary); - box-shadow: 0 8px 32px rgba(99, 102, 241, 0.3); -} - -.focus-task-floating-timer.focus-task-break-mode { - border-color: var(--ft-break); - box-shadow: 0 8px 32px rgba(6, 182, 212, 0.3); -} - -.focus-task-floating-inner { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; -} - -.focus-task-floating-task { - font-size: 12px; - color: var(--text-muted); - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.focus-task-floating-time { - font-size: 28px; - font-weight: 700; - font-variant-numeric: tabular-nums; - color: var(--ft-primary); -} - -.focus-task-floating-time.focus-task-overtime { - color: var(--ft-warning); -} - -.focus-task-floating-controls { - display: flex; - gap: 8px; -} - -.focus-task-float-btn { - width: 36px; - height: 36px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - background: var(--ft-bg-secondary); - border: 1px solid var(--ft-border); - cursor: pointer; - font-size: 14px; - transition: all 0.2s ease; -} - -.focus-task-float-btn:hover { - background: var(--ft-primary); - color: white; - border-color: var(--ft-primary); +.focus-task-status-bar.focus-task-status-active { + color: var(--text-accent); + font-weight: 500; } /* ============ Modal Styles ============ */ diff --git a/versions.json b/versions.json index af9a39e..1b5630e 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.0.0": "0.15.0" + "1.0.1": "0.15.0" }