feat: add wirings - view and ui

This commit is contained in:
2026-07-14 10:05:00 +05:00
parent 3490781ad5
commit cbb97a99ce
9 changed files with 344 additions and 107 deletions
+7
View File
@@ -0,0 +1,7 @@
import * as vscode from "vscode";
import { AppError } from "@/error";
/** Thin UI adapter: show application error to the user. */
export function presentError(error: AppError): void {
void vscode.window.showErrorMessage(error.message);
}
+40
View File
@@ -0,0 +1,40 @@
import * as vscode from "vscode";
import { ITaskRepository } from "@/ports";
import { TaskTreeItem } from "@/views/taskTreeItem";
import { TASK_STATUS_LABELS } from "./taskLabels";
/**
* Resolve task id from a TreeView item, or ask the user via QuickPick
* when the command is invoked from the Command Palette.
*/
export async function resolveTaskId(
repo: ITaskRepository,
folderPath: string | undefined,
item?: unknown,
): Promise<string | undefined> {
if (item instanceof TaskTreeItem) {
return item.task.id;
}
if (folderPath === undefined) {
return undefined;
}
const tasks = await repo.list(folderPath);
if (tasks.length === 0) {
void vscode.window.showInformationMessage("No tasks found");
return undefined;
}
const picked = await vscode.window.showQuickPick(
tasks.map((task) => ({
label: task.title,
description: TASK_STATUS_LABELS[task.status],
detail: task.priority,
taskId: task.id,
})),
{ placeHolder: "Select a task" },
);
return picked?.taskId;
}
+169
View File
@@ -0,0 +1,169 @@
import * as vscode from "vscode";
import { changeStatus } from "@/commands/changeStatus";
import { createTask } from "@/commands/createTask";
import { deleteTask } from "@/commands/deleteTask";
import { openTask } from "@/commands/openTask";
import { appError, AppErrorVariant } from "@/error";
import { TaskStatus } from "@/model/task";
import { IConfigProvider, ITaskRepository } from "@/ports";
import { TaskTreeProvider } from "@/views/taskTreeProvider";
import { presentError } from "./presentError";
import { resolveTaskId } from "./resolveTaskId";
import { TASK_STATUS_LABELS, TASK_STATUS_ORDER } from "./taskLabels";
export type TaskCommandDeps = {
repo: ITaskRepository;
config: IConfigProvider;
tree: TaskTreeProvider;
};
export async function runCreateTask(deps: TaskCommandDeps): Promise<void> {
const title = await vscode.window.showInputBox({
prompt: "Task title",
placeHolder: "Enter task title...",
});
// InputBox cancel is not an application error.
if (title === undefined) {
return;
}
const result = await createTask(deps.repo, {
folderPath: deps.config.getProjectTaskPath(),
title,
});
if (result.isErr()) {
presentError(result.error);
return;
}
deps.tree.refresh();
void vscode.window.showInformationMessage(
`Task created: ${result.value.title}`,
);
}
export async function runOpenTask(
deps: TaskCommandDeps,
item?: unknown,
): Promise<void> {
const folderPath = deps.config.getProjectTaskPath();
if (folderPath === undefined) {
presentError(appError(AppErrorVariant.NO_FOLDER));
return;
}
const id = await resolveTaskId(deps.repo, folderPath, item);
if (id === undefined) {
return;
}
const result = await openTask(deps.repo, { folderPath, id });
if (result.isErr()) {
presentError(result.error);
return;
}
const uri = vscode.Uri.file(result.value.path);
await vscode.window.showTextDocument(uri);
}
export async function runDeleteTask(
deps: TaskCommandDeps,
item?: unknown,
): Promise<void> {
const folderPath = deps.config.getProjectTaskPath();
if (folderPath === undefined) {
presentError(appError(AppErrorVariant.NO_FOLDER));
return;
}
const id = await resolveTaskId(deps.repo, folderPath, item);
if (id === undefined) {
return;
}
const confirm = await vscode.window.showWarningMessage(
"Delete this task?",
{ modal: true },
"Delete",
);
if (confirm !== "Delete") {
return;
}
const result = await deleteTask(deps.repo, { folderPath, id });
if (result.isErr()) {
presentError(result.error);
return;
}
deps.tree.refresh();
void vscode.window.showInformationMessage("Task deleted");
}
export async function runChangeStatus(
deps: TaskCommandDeps,
item?: unknown,
): Promise<void> {
const folderPath = deps.config.getProjectTaskPath();
if (folderPath === undefined) {
presentError(appError(AppErrorVariant.NO_FOLDER));
return;
}
const id = await resolveTaskId(deps.repo, folderPath, item);
if (id === undefined) {
return;
}
const picked = await vscode.window.showQuickPick(
TASK_STATUS_ORDER.map((value) => ({
label: TASK_STATUS_LABELS[value],
status: value as TaskStatus,
})),
{ placeHolder: "New status" },
);
if (!picked) {
return;
}
const result = await changeStatus(deps.repo, {
folderPath,
id,
status: picked.status,
});
if (result.isErr()) {
presentError(result.error);
return;
}
deps.tree.refresh();
void vscode.window.showInformationMessage(
`Status: ${TASK_STATUS_LABELS[result.value.status]}`,
);
}
/** Register all task command adapters on the extension host. */
export function registerTaskCommands(
context: vscode.ExtensionContext,
deps: TaskCommandDeps,
): void {
context.subscriptions.push(
vscode.commands.registerCommand("projectTasks.createTask", () =>
runCreateTask(deps),
),
vscode.commands.registerCommand(
"projectTasks.openTask",
(item?: unknown) => runOpenTask(deps, item),
),
vscode.commands.registerCommand(
"projectTasks.deleteTask",
(item?: unknown) => runDeleteTask(deps, item),
),
vscode.commands.registerCommand(
"projectTasks.changeStatus",
(item?: unknown) => runChangeStatus(deps, item),
),
);
}
+15
View File
@@ -0,0 +1,15 @@
import { TaskStatus } from "@/model/task";
export const TASK_STATUS_ORDER: TaskStatus[] = [
"todo",
"in-progress",
"done",
"cancelled",
];
export const TASK_STATUS_LABELS: Record<TaskStatus, string> = {
todo: "To Do",
"in-progress": "In Progress",
done: "Done",
cancelled: "Cancelled",
};