feat(extension): adicionar deteccao de inatividade com auto-pause e confirmacao de retorno
This commit is contained in:
parent
e445ad565b
commit
08d735f61a
8 changed files with 204 additions and 16 deletions
2
vscode-extension/.gitignore
vendored
2
vscode-extension/.gitignore
vendored
|
|
@ -1,5 +1,5 @@
|
|||
node_modules/
|
||||
dist/
|
||||
*.vsix
|
||||
!beetask-vscode-1.0.0.vsix
|
||||
!beetask-vscode-*.vsix
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ Extensão oficial do **BeeTask** para o Visual Studio Code. Gerencie suas demand
|
|||
## ✨ Recursos
|
||||
|
||||
- **⏱️ Cronômetro em Tempo Real:** Acompanhe o tempo trabalhado na tarefa ativa direto na Barra de Status (rodapé do VS Code).
|
||||
- **🛑 Pausa Automática por Inatividade (10min):** Se você se ausentar do VS Code por mais de 10 minutos com uma tarefa rodando, a extensão pausa o cronômetro automaticamente e pergunta se você ainda está trabalhando nela ao retornar.
|
||||
- **📋 Painel de Tarefas na Barra Lateral:** Visualize rapidamente suas tarefas pendentes, em execução e concluídas na aba BeeTask.
|
||||
- **▶️ Play / ⏸️ Pause com 1 Clique:** Inicie e pause o cronômetro das suas tarefas direto do VS Code.
|
||||
- **➕ Criação Rápida de Tarefas:** Crie novas tarefas associadas a projetos e categorias sem sair do editor.
|
||||
|
|
@ -78,3 +79,5 @@ Acesse `Configurações` (`Ctrl+,`) e busque por `BeeTask`:
|
|||
- `beetask.serverUrl`: URL da API backend (Padrão: `https://api-task.beetech.dev`).
|
||||
- `beetask.webUrl`: URL do site web (Padrão: `https://task.beetech.dev`).
|
||||
- `beetask.refreshIntervalSeconds`: Intervalo de sincronização automática em segundos (Padrão: `30`).
|
||||
- `beetask.enableIdleDetection`: Ativar pausa automática da tarefa por inatividade (Padrão: `true`).
|
||||
- `beetask.idleTimeoutMinutes`: Tempo limite de inatividade em minutos (Padrão: `10`).
|
||||
|
|
|
|||
Binary file not shown.
BIN
vscode-extension/beetask-vscode-1.1.0.vsix
Normal file
BIN
vscode-extension/beetask-vscode-1.1.0.vsix
Normal file
Binary file not shown.
|
|
@ -2,7 +2,7 @@
|
|||
"name": "beetask-vscode",
|
||||
"displayName": "BeeTask",
|
||||
"description": "Gerenciamento e rastreamento de tarefas com cronômetro integrado ao VS Code",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"publisher": "beetech",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -153,6 +153,16 @@
|
|||
"type": "number",
|
||||
"default": 30,
|
||||
"description": "Intervalo de atualização automática das tarefas (em segundos)"
|
||||
},
|
||||
"beetask.enableIdleDetection": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Ativar pausa automática da tarefa por inatividade no VS Code"
|
||||
},
|
||||
"beetask.idleTimeoutMinutes": {
|
||||
"type": "number",
|
||||
"default": 10,
|
||||
"description": "Tempo limite de inatividade em minutos para pausar a tarefa ativa (padrão: 10 minutos)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { BeeTaskApiClient } from './api';
|
|||
import { AuthManager } from './authManager';
|
||||
import { StatusBarManager } from './statusBarManager';
|
||||
import { TaskTreeProvider, TaskTreeItem } from './taskTreeProvider';
|
||||
import { IdleDetector } from './idleDetector';
|
||||
import { Task } from './types';
|
||||
|
||||
export function registerCommands(
|
||||
|
|
@ -10,7 +11,8 @@ export function registerCommands(
|
|||
api: BeeTaskApiClient,
|
||||
authManager: AuthManager,
|
||||
statusBar: StatusBarManager,
|
||||
treeProvider: TaskTreeProvider
|
||||
treeProvider: TaskTreeProvider,
|
||||
idleDetector?: IdleDetector
|
||||
): void {
|
||||
// 1. LOGIN
|
||||
context.subscriptions.push(
|
||||
|
|
@ -56,7 +58,7 @@ export function registerCommands(
|
|||
}
|
||||
);
|
||||
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
|
||||
// Prompt org selection if multiple
|
||||
const orgs = authManager.getOrganizations();
|
||||
|
|
@ -109,7 +111,7 @@ export function registerCommands(
|
|||
if (selected) {
|
||||
await authManager.setActiveOrgId(selected.orgId);
|
||||
vscode.window.showInformationMessage(`Organização ativa alterada para: ${selected.label}`);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
|
@ -117,7 +119,7 @@ export function registerCommands(
|
|||
// 4. REFRESH TASKS
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('beetask.refreshTasks', async () => {
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
vscode.window.setStatusBarMessage('BeeTask: Tarefas atualizadas.', 2000);
|
||||
})
|
||||
);
|
||||
|
|
@ -155,7 +157,7 @@ export function registerCommands(
|
|||
try {
|
||||
await api.startTask(task.id);
|
||||
vscode.window.showInformationMessage(`▶️ Iniciada: #${task.id} ${task.title}`);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Falha ao iniciar tarefa: ${err.message}`);
|
||||
}
|
||||
|
|
@ -182,7 +184,7 @@ export function registerCommands(
|
|||
try {
|
||||
await api.stopTask(task.id);
|
||||
vscode.window.showInformationMessage(`⏸️ Pausada: #${task.id} ${task.title}`);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Falha ao pausar tarefa: ${err.message}`);
|
||||
}
|
||||
|
|
@ -215,7 +217,7 @@ export function registerCommands(
|
|||
try {
|
||||
await api.updateStatus(task.id, 'COMPLETED');
|
||||
vscode.window.showInformationMessage(`✔️ Tarefa #${task.id} concluída com sucesso!`);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Falha ao concluir tarefa: ${err.message}`);
|
||||
}
|
||||
|
|
@ -279,7 +281,7 @@ export function registerCommands(
|
|||
});
|
||||
|
||||
vscode.window.showInformationMessage(`Tarefa #${created.id} criada com sucesso!`);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
|
||||
const startNow = await vscode.window.showInformationMessage(
|
||||
`Deseja iniciar o cronômetro para #${created.id} agora?`,
|
||||
|
|
@ -288,7 +290,7 @@ export function registerCommands(
|
|||
);
|
||||
if (startNow === 'Sim, Iniciar') {
|
||||
await api.startTask(created.id);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
}
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Falha ao criar tarefa: ${err.message}`);
|
||||
|
|
@ -361,7 +363,7 @@ export function registerCommands(
|
|||
});
|
||||
|
||||
vscode.window.showInformationMessage(`✨ Tarefa #${created.id} criada via IA!`);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
|
||||
const startNow = await vscode.window.showInformationMessage(
|
||||
`Deseja iniciar o cronômetro para #${created.id}?`,
|
||||
|
|
@ -370,7 +372,7 @@ export function registerCommands(
|
|||
);
|
||||
if (startNow === 'Sim, Iniciar') {
|
||||
await api.startTask(created.id);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider);
|
||||
await refreshAll(api, authManager, statusBar, treeProvider, idleDetector);
|
||||
}
|
||||
} catch (err: any) {
|
||||
vscode.window.showErrorMessage(`Falha na criação com IA: ${err.message}`);
|
||||
|
|
@ -526,8 +528,10 @@ async function refreshAll(
|
|||
api: BeeTaskApiClient,
|
||||
authManager: AuthManager,
|
||||
statusBar: StatusBarManager,
|
||||
treeProvider: TaskTreeProvider
|
||||
treeProvider: TaskTreeProvider,
|
||||
idleDetector?: IdleDetector
|
||||
): Promise<void> {
|
||||
idleDetector?.recordActivity();
|
||||
const isAuth = await authManager.isAuthenticated();
|
||||
if (!isAuth) {
|
||||
statusBar.setActiveTask(null);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { AuthManager } from './authManager';
|
|||
import { BeeTaskApiClient } from './api';
|
||||
import { StatusBarManager } from './statusBarManager';
|
||||
import { TaskTreeProvider } from './taskTreeProvider';
|
||||
import { IdleDetector } from './idleDetector';
|
||||
import { registerCommands } from './commands';
|
||||
|
||||
let pollTimer: NodeJS.Timeout | null = null;
|
||||
|
|
@ -12,17 +13,19 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||
const api = new BeeTaskApiClient(authManager);
|
||||
const statusBar = new StatusBarManager();
|
||||
const treeProvider = new TaskTreeProvider(api, authManager);
|
||||
const idleDetector = new IdleDetector(api, authManager, statusBar, treeProvider);
|
||||
|
||||
// Register Tree View
|
||||
// Register Tree View & Status Bar & Idle Detector
|
||||
const treeView = vscode.window.createTreeView('beetask.tasksView', {
|
||||
treeDataProvider: treeProvider,
|
||||
showCollapseAll: true,
|
||||
});
|
||||
context.subscriptions.push(treeView);
|
||||
context.subscriptions.push(statusBar);
|
||||
context.subscriptions.push(idleDetector);
|
||||
|
||||
// Register Commands
|
||||
registerCommands(context, api, authManager, statusBar, treeProvider);
|
||||
registerCommands(context, api, authManager, statusBar, treeProvider, idleDetector);
|
||||
|
||||
// Initial Sync
|
||||
if (await authManager.isAuthenticated()) {
|
||||
|
|
|
|||
168
vscode-extension/src/idleDetector.ts
Normal file
168
vscode-extension/src/idleDetector.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { BeeTaskApiClient } from './api';
|
||||
import { AuthManager } from './authManager';
|
||||
import { StatusBarManager } from './statusBarManager';
|
||||
import { TaskTreeProvider } from './taskTreeProvider';
|
||||
import { Task } from './types';
|
||||
|
||||
export class IdleDetector implements vscode.Disposable {
|
||||
private lastActivityTime: number = Date.now();
|
||||
private checkInterval: NodeJS.Timeout | null = null;
|
||||
private isPrompting: boolean = false;
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
|
||||
private api: BeeTaskApiClient;
|
||||
private authManager: AuthManager;
|
||||
private statusBar: StatusBarManager;
|
||||
private treeProvider: TaskTreeProvider;
|
||||
|
||||
constructor(
|
||||
api: BeeTaskApiClient,
|
||||
authManager: AuthManager,
|
||||
statusBar: StatusBarManager,
|
||||
treeProvider: TaskTreeProvider
|
||||
) {
|
||||
this.api = api;
|
||||
this.authManager = authManager;
|
||||
this.statusBar = statusBar;
|
||||
this.treeProvider = treeProvider;
|
||||
|
||||
this.registerActivityListeners();
|
||||
this.startPeriodicCheck();
|
||||
}
|
||||
|
||||
public recordActivity(): void {
|
||||
this.lastActivityTime = Date.now();
|
||||
}
|
||||
|
||||
private registerActivityListeners(): void {
|
||||
// 1. Digitação ou alteração de código
|
||||
this.disposables.push(
|
||||
vscode.workspace.onDidChangeTextDocument(() => this.recordActivity())
|
||||
);
|
||||
|
||||
// 2. Movimentação de cursor ou seleção de texto
|
||||
this.disposables.push(
|
||||
vscode.window.onDidChangeTextEditorSelection(() => this.recordActivity())
|
||||
);
|
||||
|
||||
// 3. Troca de arquivo / aba ativa
|
||||
this.disposables.push(
|
||||
vscode.window.onDidChangeActiveTextEditor(() => this.recordActivity())
|
||||
);
|
||||
|
||||
// 4. Rolagem da página (scroll)
|
||||
this.disposables.push(
|
||||
vscode.window.onDidChangeTextEditorVisibleRanges(() => this.recordActivity())
|
||||
);
|
||||
|
||||
// 5. Salvamento de arquivo
|
||||
this.disposables.push(
|
||||
vscode.workspace.onDidSaveTextDocument(() => this.recordActivity())
|
||||
);
|
||||
|
||||
// 6. Janela do VS Code ganhou foco
|
||||
this.disposables.push(
|
||||
vscode.window.onDidChangeWindowState((state) => {
|
||||
if (state.focused) {
|
||||
this.recordActivity();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private startPeriodicCheck(): void {
|
||||
// Checa a cada 15 segundos
|
||||
this.checkInterval = setInterval(async () => {
|
||||
await this.checkIdleStatus();
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
private async checkIdleStatus(): Promise<void> {
|
||||
const config = vscode.workspace.getConfiguration('beetask');
|
||||
const enabled = config.get<boolean>('enableIdleDetection', true);
|
||||
const timeoutMinutes = config.get<number>('idleTimeoutMinutes', 10);
|
||||
|
||||
if (!enabled || timeoutMinutes <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeTask = this.statusBar.getActiveTask();
|
||||
if (!activeTask) {
|
||||
// Se não há tarefa rodando, mantém o relógio zerado para a próxima
|
||||
this.lastActivityTime = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isPrompting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idleMillis = Date.now() - this.lastActivityTime;
|
||||
const idleMinutes = idleMillis / 60000;
|
||||
|
||||
if (idleMinutes >= timeoutMinutes) {
|
||||
await this.handleIdlePause(activeTask, timeoutMinutes);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleIdlePause(task: Task, timeoutMinutes: number): Promise<void> {
|
||||
this.isPrompting = true;
|
||||
console.log(`[BeeTask] Inatividade de ${timeoutMinutes}min detectada. Pausando tarefa #${task.id}...`);
|
||||
|
||||
try {
|
||||
// 1. Pausa imediatamente a tarefa no servidor
|
||||
await this.api.stopTask(task.id);
|
||||
this.statusBar.setActiveTask(null);
|
||||
this.treeProvider.refresh();
|
||||
|
||||
// 2. Pergunta ao usuário se ele ainda está trabalhando nela
|
||||
const question = `Você ainda está trabalhando na tarefa #${task.id} - ${task.title}?`;
|
||||
const choice = await vscode.window.showWarningMessage(
|
||||
question,
|
||||
{ modal: false },
|
||||
'Sim',
|
||||
'Não'
|
||||
);
|
||||
|
||||
if (choice === 'Sim') {
|
||||
// Usuário confirmou que ainda está trabalhando -> retoma a tarefa
|
||||
await this.api.startTask(task.id);
|
||||
vscode.window.showInformationMessage(`▶️ Tarefa #${task.id} retomada!`);
|
||||
} else {
|
||||
// Clicou "Não" ou fechou a notificação -> mantém pausada
|
||||
vscode.window.showInformationMessage(`⏸️ Tarefa #${task.id} mantida pausada.`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('[BeeTask] Erro ao processar pausa por inatividade:', err);
|
||||
} finally {
|
||||
// Reinicia o relógio de atividade para não dar loop
|
||||
this.recordActivity();
|
||||
this.isPrompting = false;
|
||||
|
||||
// Sincroniza estado visual final
|
||||
try {
|
||||
const orgId = this.authManager.getActiveOrgId();
|
||||
const tasks = await this.api.listMyTasks(orgId);
|
||||
let runningTask = null;
|
||||
for (const t of tasks) {
|
||||
if (t.history && t.history.some(h => h.startAt && !h.endAt)) {
|
||||
runningTask = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.statusBar.setActiveTask(runningTask);
|
||||
this.treeProvider.refresh();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.checkInterval) {
|
||||
clearInterval(this.checkInterval);
|
||||
this.checkInterval = null;
|
||||
}
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
this.disposables = [];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue