Polished timer and UI

This commit is contained in:
2025-12-17 21:03:47 +01:00
parent 218992cf60
commit de23d34d49
3 changed files with 263 additions and 45 deletions

View File

@@ -53,6 +53,98 @@
background-color: var(--background-modifier-error-hover); background-color: var(--background-modifier-error-hover);
} }
.taskweaver-active-timer {
background: linear-gradient(135deg, var(--background-secondary) 0%, var(--background-primary-alt) 100%);
border: 2px solid var(--interactive-accent);
border-radius: var(--radius-l);
padding: var(--size-4-4);
margin-bottom: var(--size-4-4);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.taskweaver-active-timer-header {
text-align: center;
margin-bottom: var(--size-4-2);
}
.taskweaver-active-timer-label {
font-size: var(--font-ui-smaller);
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-muted);
font-weight: var(--font-semibold);
}
.taskweaver-active-timer-title {
text-align: center;
font-size: var(--font-ui-large);
font-weight: var(--font-semibold);
color: var(--text-normal);
margin-bottom: var(--size-4-3);
}
.taskweaver-active-timer-display {
text-align: center;
font-size: 48px;
font-family: var(--font-monospace);
font-weight: var(--font-bold);
color: var(--interactive-accent);
margin: var(--size-4-4) 0;
letter-spacing: 2px;
}
.taskweaver-active-timer-controls {
display: flex;
gap: var(--size-4-2);
justify-content: center;
margin-top: var(--size-4-3);
}
.taskweaver-timer-control-btn {
padding: var(--size-4-2) var(--size-4-4);
border: none;
border-radius: var(--radius-m);
cursor: pointer;
font-size: var(--font-ui-medium);
font-weight: var(--font-semibold);
transition: all 0.2s;
flex: 1;
max-width: 120px;
}
.taskweaver-timer-control-btn.pause {
background-color: var(--color-orange);
color: white;
}
.taskweaver-timer-control-btn.pause:hover {
background-color: var(--color-orange);
opacity: 0.8;
transform: translateY(-2px);
}
.taskweaver-timer-control-btn.stop {
background-color: var(--background-modifier-error);
color: white;
}
.taskweaver-timer-control-btn.stop:hover {
background-color: var(--background-modifier-error);
opacity: 0.8;
transform: translateY(-2px);
}
.taskweaver-timer-control-btn.complete {
background-color: var(--color-green);
color: white;
}
.taskweaver-timer-control-btn.complete:hover {
background-color: var(--color-green);
opacity: 0.8;
transform: translateY(-2px);
}
.taskweaver-category { .taskweaver-category {
margin-bottom: var(--size-4-3); margin-bottom: var(--size-4-3);
background-color: var(--background-secondary); background-color: var(--background-secondary);
@@ -123,32 +215,47 @@
gap: var(--size-4-1); gap: var(--size-4-1);
} }
.taskweaver-timer { .taskweaver-task-running {
opacity: 0.6;
background-color: var(--background-primary-alt) !important;
}
.taskweaver-timer-small {
font-family: var(--font-monospace); font-family: var(--font-monospace);
color: var(--text-muted); color: var(--text-muted);
font-size: var(--font-ui-smaller); font-size: var(--font-ui-smaller);
min-width: 70px; min-width: 60px;
text-align: right; text-align: right;
} }
.taskweaver-timer-btn, .taskweaver-start-btn {
background-color: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
border-radius: var(--radius-s);
padding: var(--size-2-1) var(--size-4-2);
cursor: pointer;
font-size: var(--font-ui-small);
transition: all 0.2s;
font-weight: var(--font-semibold);
}
.taskweaver-start-btn:hover {
background-color: var(--interactive-accent-hover);
transform: translateY(-1px);
}
.taskweaver-delete-btn { .taskweaver-delete-btn {
background: transparent; background: transparent;
border: 1px solid var(--background-modifier-border); border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-s); border-radius: var(--radius-s);
padding: var(--size-2-1) var(--size-2-3); padding: var(--size-2-1) var(--size-2-3);
cursor: pointer; cursor: pointer;
font-size: var(--font-ui-small); font-size: var(--font-ui-smaller);
color: var(--text-normal); color: var(--text-muted);
transition: all 0.2s; transition: all 0.2s;
} }
.taskweaver-timer-btn:hover {
background-color: var(--interactive-accent);
color: var(--text-on-accent);
border-color: var(--interactive-accent);
}
.taskweaver-delete-btn:hover { .taskweaver-delete-btn:hover {
background-color: var(--background-modifier-error); background-color: var(--background-modifier-error);
color: var(--text-on-accent); color: var(--text-on-accent);

View File

@@ -1,4 +1,4 @@
import { App, TFile } from "obsidian"; import { App, TFile, Notice, moment } from "obsidian";
import { Task, Category } from "./types"; import { Task, Category } from "./types";
export function formatDuration(milliseconds: number): string { export function formatDuration(milliseconds: number): string {
@@ -20,32 +20,64 @@ export function generateUniqueId(): string {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
} }
async function getDailyNote(app: App): Promise<TFile> { async function getDailyNote(app: App): Promise<TFile | null> {
const { vault } = app; const { vault } = app;
// @ts-ignore - Access internal daily notes API // @ts-ignore - Access internal plugins
const dailyNotesPlugin = app.internalPlugins.plugins["daily-notes"]; const dailyNotesPlugin = app.internalPlugins.plugins["daily-notes"];
if (!dailyNotesPlugin || !dailyNotesPlugin.enabled) { if (!dailyNotesPlugin || !dailyNotesPlugin.enabled) {
throw new Error("Daily notes plugin is not enabled. Please enable it in Settings → Core plugins."); new Notice("Daily notes plugin is not enabled. Please enable it in Settings → Core plugins.");
return null;
} }
// @ts-ignore - Access daily notes interface // @ts-ignore - Access the daily notes instance
const { createDailyNote, getDailyNote, getAllDailyNotes } = app.internalPlugins.plugins["daily-notes"].instance; const dailyNotesInterface = dailyNotesPlugin.instance;
// @ts-ignore if (!dailyNotesInterface) {
const dailyNotes = getAllDailyNotes(); new Notice("Could not access daily notes interface");
// @ts-ignore return null;
const today = window.moment();
// @ts-ignore
let dailyNote = getDailyNote(today, dailyNotes);
if (!dailyNote) {
// @ts-ignore
dailyNote = await createDailyNote(today);
} }
try {
// @ts-ignore
const { format, folder, template } = dailyNotesInterface.options || {};
const dateFormat = format || "YYYY-MM-DD";
const dailyNotesFolder = folder || "";
const today = moment().format(dateFormat);
const fileName = `${today}.md`;
const filePath = dailyNotesFolder ? `${dailyNotesFolder}/${fileName}` : fileName;
let dailyNote = vault.getAbstractFileByPath(filePath);
if (!dailyNote || !(dailyNote instanceof TFile)) {
if (dailyNotesFolder && !vault.getAbstractFileByPath(dailyNotesFolder)) {
await vault.createFolder(dailyNotesFolder);
}
let initialContent = "";
if (template) {
const templateFile = vault.getAbstractFileByPath(template);
if (templateFile instanceof TFile) {
initialContent = await vault.read(templateFile);
}
}
dailyNote = await vault.create(filePath, initialContent);
}
if (dailyNote instanceof TFile) {
return dailyNote; return dailyNote;
}
return null;
} catch (error) {
console.error("Error getting daily note:", error);
new Notice("Error accessing daily note: " + error.message);
return null;
}
} }
export async function logTaskToDailyNote( export async function logTaskToDailyNote(
@@ -57,6 +89,11 @@ export async function logTaskToDailyNote(
try { try {
const dailyNote = await getDailyNote(app); const dailyNote = await getDailyNote(app);
if (!dailyNote) {
new Notice("Could not find or create daily note");
return;
}
const duration = formatDuration(task.totalElapsed); const duration = formatDuration(task.totalElapsed);
const logEntry = format const logEntry = format
.replace("{{title}}", task.title) .replace("{{title}}", task.title)
@@ -67,8 +104,10 @@ export async function logTaskToDailyNote(
const content = await app.vault.read(dailyNote); const content = await app.vault.read(dailyNote);
const newContent = content ? `${content}\n${logEntry}` : logEntry; const newContent = content ? `${content}\n${logEntry}` : logEntry;
await app.vault.modify(dailyNote, newContent); await app.vault.modify(dailyNote, newContent);
new Notice("Task logged to daily note");
} catch (error) { } catch (error) {
console.error("Failed to log task to daily note:", error); console.error("Failed to log task to daily note:", error);
throw error; new Notice("Failed to log task: " + error.message);
} }
} }

100
view.ts
View File

@@ -63,6 +63,12 @@ export class TaskWeaverView extends ItemView {
const categoriesMap = new Map(this.plugin.settings.categories.map(c => [c.id, c])); const categoriesMap = new Map(this.plugin.settings.categories.map(c => [c.id, c]));
const activeTask = this.plugin.settings.tasks.find(t => t.startTime !== null);
if (activeTask) {
const category = categoriesMap.get(activeTask.categoryId);
this.renderActiveTimer(container, activeTask, category);
}
this.plugin.settings.categories.forEach(category => { this.plugin.settings.categories.forEach(category => {
const categoryTasks = this.plugin.settings.tasks.filter(t => t.categoryId === category.id && !t.completed); const categoryTasks = this.plugin.settings.tasks.filter(t => t.categoryId === category.id && !t.completed);
@@ -84,6 +90,55 @@ export class TaskWeaverView extends ItemView {
} }
} }
renderActiveTimer(container: HTMLElement, task: Task, category?: Category): void {
const timerSection = container.createDiv({ cls: "taskweaver-active-timer" });
if (category) {
timerSection.style.borderColor = category.color;
}
const timerHeader = timerSection.createDiv({ cls: "taskweaver-active-timer-header" });
timerHeader.createEl("span", { text: "Active Task", cls: "taskweaver-active-timer-label" });
const taskTitleDiv = timerSection.createDiv({ cls: "taskweaver-active-timer-title" });
if (category?.emoji) {
taskTitleDiv.createSpan({ text: `${category.emoji} `, cls: "taskweaver-emoji" });
}
taskTitleDiv.createSpan({ text: task.title });
const timerDisplay = timerSection.createDiv({
cls: "taskweaver-active-timer-display",
attr: { "data-task-id": task.id }
});
timerDisplay.setText(formatDuration(this.plugin.getTaskElapsed(task)));
const timerControls = timerSection.createDiv({ cls: "taskweaver-active-timer-controls" });
const pauseBtn = timerControls.createEl("button", {
text: "Pause",
cls: "taskweaver-timer-control-btn pause"
});
pauseBtn.onclick = async () => {
await this.plugin.toggleTaskTimer(task.id);
};
const stopBtn = timerControls.createEl("button", {
text: "Stop",
cls: "taskweaver-timer-control-btn stop"
});
stopBtn.onclick = async () => {
await this.plugin.toggleTaskTimer(task.id);
};
const completeBtn = timerControls.createEl("button", {
text: "Complete",
cls: "taskweaver-timer-control-btn complete"
});
completeBtn.onclick = async () => {
await this.plugin.toggleTaskComplete(task.id);
};
}
renderCategory(container: HTMLElement, category: Category, tasks: Task[]): void { renderCategory(container: HTMLElement, category: Category, tasks: Task[]): void {
const categoryDiv = container.createDiv({ cls: "taskweaver-category" }); const categoryDiv = container.createDiv({ cls: "taskweaver-category" });
@@ -104,14 +159,11 @@ export class TaskWeaverView extends ItemView {
renderTask(container: HTMLElement, task: Task, category?: Category): void { renderTask(container: HTMLElement, task: Task, category?: Category): void {
const taskDiv = container.createDiv({ cls: "taskweaver-task" }); const taskDiv = container.createDiv({ cls: "taskweaver-task" });
const taskInfo = taskDiv.createDiv({ cls: "taskweaver-task-info" }); if (task.startTime) {
taskDiv.addClass("taskweaver-task-running");
}
const checkbox = taskInfo.createEl("input", { type: "checkbox" }); const taskInfo = taskDiv.createDiv({ cls: "taskweaver-task-info" });
checkbox.checked = task.completed;
checkbox.addClass("taskweaver-checkbox");
checkbox.onclick = async () => {
await this.plugin.toggleTaskComplete(task.id);
};
const taskTitle = taskInfo.createSpan({ const taskTitle = taskInfo.createSpan({
text: task.title, text: task.title,
@@ -121,34 +173,54 @@ export class TaskWeaverView extends ItemView {
const taskControls = taskDiv.createDiv({ cls: "taskweaver-task-controls" }); const taskControls = taskDiv.createDiv({ cls: "taskweaver-task-controls" });
const timerSpan = taskControls.createSpan({ const timerSpan = taskControls.createSpan({
cls: "taskweaver-timer", cls: "taskweaver-timer-small",
attr: { "data-task-id": task.id } attr: { "data-task-id": task.id }
}); });
timerSpan.setText(formatDuration(this.plugin.getTaskElapsed(task))); timerSpan.setText(formatDuration(this.plugin.getTaskElapsed(task)));
if (!task.completed) { if (!task.completed) {
const timerBtn = taskControls.createEl("button", { if (!task.startTime) {
text: task.startTime ? "⏸" : "▶", const startBtn = taskControls.createEl("button", {
cls: "taskweaver-timer-btn" text: "Start",
cls: "taskweaver-start-btn"
}); });
timerBtn.onclick = async () => { startBtn.onclick = async () => {
await this.plugin.toggleTaskTimer(task.id); await this.plugin.toggleTaskTimer(task.id);
}; };
}
const deleteBtn = taskControls.createEl("button", { const deleteBtn = taskControls.createEl("button", {
text: "🗑", text: "Delete",
cls: "taskweaver-delete-btn" cls: "taskweaver-delete-btn"
}); });
deleteBtn.onclick = async () => { deleteBtn.onclick = async () => {
await this.plugin.deleteTask(task.id); await this.plugin.deleteTask(task.id);
}; };
} else {
const checkbox = taskControls.createEl("input", { type: "checkbox" });
checkbox.checked = true;
checkbox.addClass("taskweaver-checkbox");
checkbox.onclick = async () => {
await this.plugin.toggleTaskComplete(task.id);
};
} }
} }
updateTimers(): void { updateTimers(): void {
const container = this.containerEl.children[1] as HTMLElement; const container = this.containerEl.children[1] as HTMLElement;
const timerElements = container.querySelectorAll(".taskweaver-timer");
const activeTimerDisplay = container.querySelector(".taskweaver-active-timer-display");
if (activeTimerDisplay) {
const taskId = activeTimerDisplay.getAttribute("data-task-id");
if (taskId) {
const task = this.plugin.settings.tasks.find(t => t.id === taskId);
if (task) {
activeTimerDisplay.setText(formatDuration(this.plugin.getTaskElapsed(task)));
}
}
}
const timerElements = container.querySelectorAll(".taskweaver-timer-small");
timerElements.forEach((element) => { timerElements.forEach((element) => {
const taskId = element.getAttribute("data-task-id"); const taskId = element.getAttribute("data-task-id");
if (taskId) { if (taskId) {