mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
refactor(timer): redesign timer UI with text-based interface
Replace complex button-based UI with simple text links for better integration and cleaner appearance. Improve subtask detection logic to only show timers on actual parent tasks. - Replace buttons with underlined text links for all actions - Remove complex CSS styling in favor of minimal text styling - Fix subtask detection to stop at same/lower indentation levels - Update real-time display to refresh every second - Add click event propagation prevention - Simplify UI to show "⏱ time | action | action" format - Remove 280+ lines of unnecessary CSS styling
This commit is contained in:
parent
a05f49f9f7
commit
0caa51fd54
2 changed files with 79 additions and 406 deletions
|
|
@ -65,94 +65,69 @@ class TaskTimerWidget extends WidgetType {
|
|||
return this.dom;
|
||||
}
|
||||
|
||||
this.dom = createDiv({ cls: `task-timer-widget ${this.timerState?.status || 'idle'}` });
|
||||
this.createTimeDisplay(this.dom);
|
||||
this.createButtons(this.dom);
|
||||
// Create a simple text-based widget
|
||||
this.dom = createDiv({ cls: 'task-timer-widget' });
|
||||
this.dom.style.cssText = 'margin: 2px 0; font-size: 0.9em; color: var(--text-muted);';
|
||||
this.updateTimerState();
|
||||
this.createContent();
|
||||
return this.dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create time display area
|
||||
* Create content based on timer state
|
||||
*/
|
||||
private createTimeDisplay(container: HTMLElement): void {
|
||||
const timeSpan = container.createSpan({ cls: "task-timer-display" });
|
||||
this.updateTimerState();
|
||||
this.refreshTimeDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create action buttons
|
||||
*/
|
||||
private createButtons(container: HTMLElement): void {
|
||||
const buttonContainer = container.createDiv({ cls: "task-timer-buttons" });
|
||||
private createContent(): void {
|
||||
if (!this.dom) return;
|
||||
|
||||
this.updateTimerState();
|
||||
this.dom.empty();
|
||||
|
||||
if (!this.timerState || this.timerState.status === 'idle') {
|
||||
const startButton = buttonContainer.createEl("button", {
|
||||
cls: "task-timer-button start",
|
||||
text: "Start Task"
|
||||
});
|
||||
startButton.addEventListener("click", (e) => {
|
||||
// Create text-style start button
|
||||
const startSpan = this.dom.createSpan();
|
||||
startSpan.style.cssText = 'cursor: pointer; text-decoration: underline; color: var(--text-accent);';
|
||||
startSpan.setText('Start Task');
|
||||
startSpan.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.startTimer();
|
||||
});
|
||||
} else if (this.timerState.status === 'running') {
|
||||
const pauseButton = buttonContainer.createEl("button", {
|
||||
cls: "task-timer-button pause",
|
||||
text: "Pause"
|
||||
});
|
||||
pauseButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.pauseTimer();
|
||||
});
|
||||
|
||||
const resetButton = buttonContainer.createEl("button", {
|
||||
cls: "task-timer-button reset",
|
||||
text: "Reset"
|
||||
});
|
||||
resetButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.resetTimer();
|
||||
});
|
||||
|
||||
const completeButton = buttonContainer.createEl("button", {
|
||||
cls: "task-timer-button complete",
|
||||
text: "Complete"
|
||||
});
|
||||
completeButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.completeTimer();
|
||||
});
|
||||
} else if (this.timerState.status === 'paused') {
|
||||
const resumeButton = buttonContainer.createEl("button", {
|
||||
cls: "task-timer-button resume",
|
||||
text: "Resume"
|
||||
});
|
||||
resumeButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.resumeTimer();
|
||||
});
|
||||
|
||||
const resetButton = buttonContainer.createEl("button", {
|
||||
cls: "task-timer-button reset",
|
||||
text: "Reset"
|
||||
});
|
||||
resetButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.resetTimer();
|
||||
});
|
||||
|
||||
const completeButton = buttonContainer.createEl("button", {
|
||||
cls: "task-timer-button complete",
|
||||
text: "Complete"
|
||||
});
|
||||
completeButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
this.completeTimer();
|
||||
});
|
||||
} else {
|
||||
// Show elapsed time
|
||||
const elapsedMs = Date.now() - this.timerState.startTime + (this.timerState.elapsed || 0);
|
||||
const formattedTime = TaskTimerFormatter.formatDuration(elapsedMs, this.settings.timeFormat);
|
||||
const timeSpan = this.dom.createSpan();
|
||||
timeSpan.setText(`⏱ ${formattedTime} `);
|
||||
|
||||
// Add action links
|
||||
if (this.timerState.status === 'running') {
|
||||
this.addActionLink('Pause', () => this.pauseTimer());
|
||||
this.dom.appendText(' | ');
|
||||
this.addActionLink('Complete', () => this.completeTimer());
|
||||
} else if (this.timerState.status === 'paused') {
|
||||
this.addActionLink('Resume', () => this.resumeTimer());
|
||||
this.dom.appendText(' | ');
|
||||
this.addActionLink('Complete', () => this.completeTimer());
|
||||
}
|
||||
this.dom.appendText(' | ');
|
||||
this.addActionLink('Reset', () => this.resetTimer());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add clickable action link
|
||||
*/
|
||||
private addActionLink(text: string, action: () => void): void {
|
||||
if (!this.dom) return;
|
||||
|
||||
const link = this.dom.createSpan();
|
||||
link.style.cssText = 'cursor: pointer; text-decoration: underline; color: var(--text-accent);';
|
||||
link.setText(text);
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
action();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start timer
|
||||
|
|
@ -340,8 +315,8 @@ class TaskTimerWidget extends WidgetType {
|
|||
}
|
||||
|
||||
this.updateInterval = window.setInterval(() => {
|
||||
this.refreshTimeDisplay();
|
||||
}, 5000); // Update every 5 seconds for performance
|
||||
this.createContent(); // Update the entire content
|
||||
}, 1000); // Update every second
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -354,51 +329,14 @@ class TaskTimerWidget extends WidgetType {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh only the time display (performance optimization)
|
||||
*/
|
||||
private refreshTimeDisplay(): void {
|
||||
if (!this.dom) return;
|
||||
|
||||
const timeDisplay = this.dom.querySelector('.task-timer-display') as HTMLElement;
|
||||
if (!timeDisplay) return;
|
||||
|
||||
this.updateTimerState();
|
||||
|
||||
if (this.timerState) {
|
||||
const elapsedMs = Date.now() - this.timerState.startTime;
|
||||
const formattedTime = TaskTimerFormatter.formatDuration(elapsedMs, this.settings.timeFormat);
|
||||
|
||||
// Only update if the text has changed
|
||||
if (timeDisplay.textContent !== formattedTime) {
|
||||
timeDisplay.textContent = formattedTime;
|
||||
}
|
||||
|
||||
// Update animation class
|
||||
if (this.timerState.status === 'running') {
|
||||
timeDisplay.addClass('running');
|
||||
} else {
|
||||
timeDisplay.removeClass('running');
|
||||
}
|
||||
} else {
|
||||
timeDisplay.textContent = "00:00";
|
||||
timeDisplay.removeClass('running');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the entire UI (used when state changes significantly)
|
||||
*/
|
||||
private refreshUI(): void {
|
||||
if (!this.dom) return;
|
||||
|
||||
// Update widget class
|
||||
this.dom.className = `task-timer-widget ${this.timerState?.status || 'idle'}`;
|
||||
|
||||
// Clear and recreate content
|
||||
this.dom.empty();
|
||||
this.createTimeDisplay(this.dom);
|
||||
this.createButtons(this.dom);
|
||||
this.updateTimerState();
|
||||
this.createContent();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
|
@ -536,18 +474,33 @@ function hasSubTasks(rangeText: string, parentLineText: string): boolean {
|
|||
const parentMatch = parentLineText.match(/^(\s*)/);
|
||||
const parentIndent = parentMatch ? parentMatch[1].length : 0;
|
||||
|
||||
console.log(`[TaskTimer] Checking subtasks for parent with indent ${parentIndent}`);
|
||||
|
||||
// Check subsequent lines for subtasks
|
||||
let foundSubtask = false;
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (isTaskLine(line)) {
|
||||
const lineMatch = line.match(/^(\s*)/);
|
||||
const lineIndent = lineMatch ? lineMatch[1].length : 0;
|
||||
if (lineIndent > parentIndent) {
|
||||
return true; // Found a subtask with greater indentation
|
||||
}
|
||||
|
||||
// Skip empty lines
|
||||
if (!line.trim()) continue;
|
||||
|
||||
const lineMatch = line.match(/^(\s*)/);
|
||||
const lineIndent = lineMatch ? lineMatch[1].length : 0;
|
||||
|
||||
// If we find a line with same or less indentation that's not empty, stop checking
|
||||
if (lineIndent <= parentIndent) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this indented line is a task
|
||||
if (isTaskLine(line) && lineIndent > parentIndent) {
|
||||
console.log(`[TaskTimer] Found subtask: ${line.trim()}`);
|
||||
foundSubtask = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return foundSubtask;
|
||||
}
|
||||
|
||||
function extractBlockRef(lineText: string): string | undefined {
|
||||
|
|
|
|||
|
|
@ -1,291 +1,11 @@
|
|||
/* Task Timer Widget Styles */
|
||||
/* Task Timer Widget Styles - Simple Text-Based */
|
||||
|
||||
.task-timer-widget {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 8px;
|
||||
margin: 2px 0;
|
||||
background: var(--background-secondary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
font-size: 0.9em;
|
||||
font-family: var(--font-interface);
|
||||
color: var(--text-muted);
|
||||
line-height: 1.4;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.task-timer-widget:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
border-color: var(--background-modifier-border-hover);
|
||||
}
|
||||
|
||||
/* Button group container */
|
||||
.task-timer-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Individual button styles */
|
||||
.task-timer-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 6px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 3px;
|
||||
background: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
font-size: 0.85em;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.15s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-timer-button:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
border-color: var(--background-modifier-border-hover);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.task-timer-button:active {
|
||||
background: var(--background-modifier-active);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Specific button types */
|
||||
.task-timer-button.start {
|
||||
background: var(--color-green);
|
||||
color: var(--text-on-accent);
|
||||
border-color: var(--color-green);
|
||||
}
|
||||
|
||||
.task-timer-button.start:hover {
|
||||
background: var(--color-green-hover, var(--color-green));
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.task-timer-button.pause {
|
||||
background: var(--color-orange);
|
||||
color: var(--text-on-accent);
|
||||
border-color: var(--color-orange);
|
||||
}
|
||||
|
||||
.task-timer-button.pause:hover {
|
||||
background: var(--color-orange-hover, var(--color-orange));
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.task-timer-button.resume {
|
||||
background: var(--color-blue);
|
||||
color: var(--text-on-accent);
|
||||
border-color: var(--color-blue);
|
||||
}
|
||||
|
||||
.task-timer-button.resume:hover {
|
||||
background: var(--color-blue-hover, var(--color-blue));
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.task-timer-button.reset {
|
||||
background: var(--color-yellow);
|
||||
color: var(--text-on-accent);
|
||||
border-color: var(--color-yellow);
|
||||
}
|
||||
|
||||
.task-timer-button.reset:hover {
|
||||
background: var(--color-yellow-hover, var(--color-yellow));
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.task-timer-button.complete {
|
||||
background: var(--color-purple);
|
||||
color: var(--text-on-accent);
|
||||
border-color: var(--color-purple);
|
||||
}
|
||||
|
||||
.task-timer-button.complete:hover {
|
||||
background: var(--color-purple-hover, var(--color-purple));
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Time display area */
|
||||
.task-timer-display {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 6px;
|
||||
background: var(--background-primary-alt);
|
||||
border-radius: 3px;
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.9em;
|
||||
color: var(--text-normal);
|
||||
min-width: 60px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Status-specific styles */
|
||||
.task-timer-widget.running {
|
||||
border-color: var(--color-green);
|
||||
box-shadow: 0 0 0 1px var(--color-green-transparent, rgba(46, 160, 67, 0.2));
|
||||
}
|
||||
|
||||
.task-timer-widget.running .task-timer-display {
|
||||
color: var(--color-green);
|
||||
background: var(--color-green-transparent, rgba(46, 160, 67, 0.1));
|
||||
}
|
||||
|
||||
.task-timer-widget.paused {
|
||||
border-color: var(--color-orange);
|
||||
box-shadow: 0 0 0 1px var(--color-orange-transparent, rgba(233, 151, 63, 0.2));
|
||||
}
|
||||
|
||||
.task-timer-widget.paused .task-timer-display {
|
||||
color: var(--color-orange);
|
||||
background: var(--color-orange-transparent, rgba(233, 151, 63, 0.1));
|
||||
}
|
||||
|
||||
/* Animation for running timer */
|
||||
.task-timer-display.running {
|
||||
animation: pulse-timer 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-timer {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compact mode for smaller screens */
|
||||
.task-timer-widget.compact {
|
||||
padding: 2px 4px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.task-timer-widget.compact .task-timer-button {
|
||||
padding: 1px 4px;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.task-timer-widget.compact .task-timer-display {
|
||||
padding: 1px 4px;
|
||||
font-size: 0.8em;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
/* Dark theme adjustments */
|
||||
.theme-dark .task-timer-widget {
|
||||
background: var(--background-secondary-alpha);
|
||||
border-color: var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.theme-dark .task-timer-button {
|
||||
background: var(--background-primary-alt);
|
||||
border-color: var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.theme-dark .task-timer-display {
|
||||
background: var(--background-modifier-form-field);
|
||||
}
|
||||
|
||||
/* High contrast mode */
|
||||
@media (prefers-contrast: high) {
|
||||
.task-timer-widget {
|
||||
border-width: 2px;
|
||||
border-color: var(--text-normal);
|
||||
}
|
||||
|
||||
.task-timer-button {
|
||||
border-width: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.task-timer-widget {
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.task-timer-buttons {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.task-timer-button {
|
||||
padding: 3px 6px;
|
||||
min-height: 28px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@media print {
|
||||
.task-timer-widget {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Accessibility improvements */
|
||||
.task-timer-button:focus {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.task-timer-button:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Screen reader only text */
|
||||
.task-timer-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Loading/processing state */
|
||||
.task-timer-widget.processing {
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.task-timer-widget.processing .task-timer-button {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Error state */
|
||||
.task-timer-widget.error {
|
||||
border-color: var(--color-red);
|
||||
background: var(--color-red-transparent, rgba(233, 49, 71, 0.1));
|
||||
}
|
||||
|
||||
.task-timer-widget.error .task-timer-display {
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
/* Icon support for buttons */
|
||||
.task-timer-button .task-timer-icon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-timer-button.compact .task-timer-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
/* Remove all complex button styles - we're using text links now */
|
||||
Loading…
Reference in a new issue