Initial commit

This commit is contained in:
2025-11-22 13:34:07 +01:00
commit a652a6ac04
14 changed files with 2595 additions and 0 deletions

319
src/view.ts Normal file
View File

@@ -0,0 +1,319 @@
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';
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();
}
refresh() {
const container = this.containerEl.children[1];
container.empty();
container.addClass('focus-task-container');
// 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');
activeCard.createEl('div', { cls: 'focus-task-active-label', text: '☕ BREAK TIME' });
} else {
activeCard.createEl('div', { cls: 'focus-task-active-label', text: '🎯 FOCUSING ON' });
}
activeCard.createEl('div', { cls: 'focus-task-active-task-name', text: task.text });
// Timer display
const timerDisplay = activeCard.createEl('div', { cls: 'focus-task-timer-display' });
timerDisplay.createEl('span', {
cls: 'focus-task-timer-time',
text: this.plugin.formatTime(this.plugin.currentTimerSeconds)
});
// Progress bar
const progressWrap = activeCard.createEl('div', { cls: 'focus-task-progress-wrap' });
const progress = 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;
}
progress.style.width = `${Math.min(Math.max(progressPercent, 0), 100)}%`;
if (progressPercent >= 100) progress.addClass('focus-task-overtime');
// Time info
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)}` });
}
// 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());
if (!this.plugin.isBreakMode) {
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());
if (this.plugin.isBreakMode) {
const skipBreakBtn = controls.createEl('button', { cls: 'focus-task-btn' });
skipBreakBtn.innerHTML = '⏭ Skip Break';
skipBreakBtn.addEventListener('click', () => {
this.plugin.isBreakMode = false;
this.plugin.stopTimer();
this.refresh();
});
}
}
} 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);
});
}
}