feat: add wirings - view and ui
This commit is contained in:
+21
-22
@@ -46,27 +46,24 @@ src/
|
|||||||
├── ports.ts # IFileSystem, ITaskRepository, IConfigProvider
|
├── ports.ts # IFileSystem, ITaskRepository, IConfigProvider
|
||||||
├── error.ts # AppErrorVariant + AppError { variant, message }
|
├── error.ts # AppErrorVariant + AppError { variant, message }
|
||||||
├── model/task.ts
|
├── model/task.ts
|
||||||
├── storage/
|
├── storage/ # infrastructure adapters
|
||||||
│ ├── FsTaskRepository.ts
|
├── commands/ # use-cases (no vscode)
|
||||||
│ ├── NodeFileSystem.ts
|
├── dashboard/ # pure view-model helpers
|
||||||
│ └── VscodeConfigProvider.ts
|
├── ui/ # thin vscode adapters (input/quickpick/errors)
|
||||||
├── commands/ # use-cases без vscode UI
|
│ ├── presentError.ts
|
||||||
│ ├── createTask.ts
|
│ ├── resolveTaskId.ts
|
||||||
│ ├── openTask.ts
|
│ ├── taskLabels.ts
|
||||||
│ ├── deleteTask.ts
|
│ └── taskCommands.ts # register + run* handlers
|
||||||
│ └── changeStatus.ts
|
|
||||||
├── dashboard/ # filter/group/view + inbound message parse
|
|
||||||
├── views/
|
├── views/
|
||||||
│ └── taskTreeProvider.ts # TreeView; WebView host — отдельно
|
│ ├── taskTreeItem.ts
|
||||||
|
│ └── taskTreeProvider.ts
|
||||||
└── utils/
|
└── utils/
|
||||||
├── uuid.ts
|
|
||||||
└── markdown.ts # parse/serialize frontmatter
|
|
||||||
|
|
||||||
tests/helpers/InMemoryFileSystem.ts
|
tests/helpers/InMemoryFileSystem.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
UI (input box, quick pick, open editor) — тонкие адаптеры; бизнес-логика в
|
Слои: use-cases + ports — без vscode; `ui/*` — только ввод/вывод host API;
|
||||||
use-cases + `ITaskRepository`. Ошибки — `AppError` из `error.ts`.
|
`extension.ts` — сборка зависимостей и регистрация.
|
||||||
|
|
||||||
## План реализации
|
## План реализации
|
||||||
|
|
||||||
@@ -81,14 +78,16 @@ use-cases + `ITaskRepository`. Ошибки — `AppError` из `error.ts`.
|
|||||||
| **7. Kanban** | drag-n-drop |
|
| **7. Kanban** | drag-n-drop |
|
||||||
| **8. Публикация** | vsce publish |
|
| **8. Публикация** | vsce publish |
|
||||||
|
|
||||||
### Фаза 4 — команды (use-cases)
|
### Фаза 4 — команды (use-cases + UI wiring)
|
||||||
|
|
||||||
| Команда | Поведение |
|
| Команда | Use-case | UI adapter |
|
||||||
| -------------- | -------------------------------------------------------------- |
|
| -------------- | -------------------------------------------------------------- | ------------------------------------------- |
|
||||||
| `createTask` | title + folder → defaults todo/medium; empty title / no folder |
|
| `createTask` | title + folder → defaults todo/medium; empty title / no folder | InputBox → use-case → tree.refresh |
|
||||||
| `deleteTask` | folder + id → delete; not found |
|
| `deleteTask` | folder + id → delete; not found | confirm + TreeItem/QuickPick → use-case |
|
||||||
| `changeStatus` | folder + id + status → update; not found |
|
| `changeStatus` | folder + id + status → update; not found | status QuickPick → use-case |
|
||||||
| `openTask` | folder + id → path + task для editor; not found |
|
| `openTask` | folder + id → path + task | TreeItem click / command → showTextDocument |
|
||||||
|
|
||||||
|
Ошибки use-case → `presentError` (`AppError.message`). Cancel UI ≠ `AppError`.
|
||||||
|
|
||||||
### Фаза 5 — Task Dashboard
|
### Фаза 5 — Task Dashboard
|
||||||
|
|
||||||
|
|||||||
+34
-1
@@ -26,7 +26,23 @@
|
|||||||
"commands": [
|
"commands": [
|
||||||
{
|
{
|
||||||
"command": "projectTasks.createTask",
|
"command": "projectTasks.createTask",
|
||||||
"title": "Project Tasks: Create Task"
|
"title": "Project Tasks: Create Task",
|
||||||
|
"icon": "$(add)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "projectTasks.openTask",
|
||||||
|
"title": "Project Tasks: Open Task",
|
||||||
|
"icon": "$(go-to-file)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "projectTasks.deleteTask",
|
||||||
|
"title": "Project Tasks: Delete Task",
|
||||||
|
"icon": "$(trash)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "projectTasks.changeStatus",
|
||||||
|
"title": "Project Tasks: Change Status",
|
||||||
|
"icon": "$(sync)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "projectTasks.openDashboard",
|
"command": "projectTasks.openDashboard",
|
||||||
@@ -84,6 +100,23 @@
|
|||||||
"when": "view == projectTasks.tasks",
|
"when": "view == projectTasks.tasks",
|
||||||
"group": "navigation"
|
"group": "navigation"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"view/item/context": [
|
||||||
|
{
|
||||||
|
"command": "projectTasks.openTask",
|
||||||
|
"when": "view == projectTasks.tasks && viewItem == task",
|
||||||
|
"group": "inline@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "projectTasks.changeStatus",
|
||||||
|
"when": "view == projectTasks.tasks && viewItem == task",
|
||||||
|
"group": "1_task@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "projectTasks.deleteTask",
|
||||||
|
"when": "view == projectTasks.tasks && viewItem == task",
|
||||||
|
"group": "1_task@2"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+16
-47
@@ -3,30 +3,27 @@ import * as vscode from "vscode";
|
|||||||
import { FsTaskRepository } from "@/storage/FsTaskRepository";
|
import { FsTaskRepository } from "@/storage/FsTaskRepository";
|
||||||
import { NodeFileSystem } from "@/storage/NodeFileSystem";
|
import { NodeFileSystem } from "@/storage/NodeFileSystem";
|
||||||
import { VscodeConfigProvider } from "@/storage/VscodeConfigProvider";
|
import { VscodeConfigProvider } from "@/storage/VscodeConfigProvider";
|
||||||
|
import { registerTaskCommands } from "@/ui/taskCommands";
|
||||||
import { TaskTreeProvider } from "@/views/taskTreeProvider";
|
import { TaskTreeProvider } from "@/views/taskTreeProvider";
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
const fileSystem = new NodeFileSystem();
|
const fileSystem = new NodeFileSystem();
|
||||||
const configStore = new VscodeConfigProvider();
|
const config = new VscodeConfigProvider();
|
||||||
const taskStore = new FsTaskRepository(
|
const repo = new FsTaskRepository(fileSystem, config.getFileExtension());
|
||||||
fileSystem,
|
const tree = new TaskTreeProvider(repo, config);
|
||||||
configStore.getFileExtension(),
|
|
||||||
);
|
|
||||||
const taskTreeProvider = new TaskTreeProvider(taskStore, configStore);
|
|
||||||
|
|
||||||
vscode.window.registerTreeDataProvider(
|
context.subscriptions.push(
|
||||||
"projectTasks.tasks",
|
vscode.window.registerTreeDataProvider("projectTasks.tasks", tree),
|
||||||
taskTreeProvider,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const projectPath = configStore.getProjectTaskPath();
|
const projectPath = config.getProjectTaskPath();
|
||||||
if (projectPath) {
|
if (projectPath) {
|
||||||
const ext = configStore.getFileExtension();
|
const ext = config.getFileExtension();
|
||||||
const taskPattern = path.join(projectPath, `*${ext}`).replace(/\\/g, "/");
|
const taskPattern = path.join(projectPath, `*${ext}`).replace(/\\/g, "/");
|
||||||
const watcher = vscode.workspace.createFileSystemWatcher(taskPattern);
|
const watcher = vscode.workspace.createFileSystemWatcher(taskPattern);
|
||||||
watcher.onDidCreate(() => taskTreeProvider.refresh());
|
watcher.onDidCreate(() => tree.refresh());
|
||||||
watcher.onDidChange(() => taskTreeProvider.refresh());
|
watcher.onDidChange(() => tree.refresh());
|
||||||
watcher.onDidDelete(() => taskTreeProvider.refresh());
|
watcher.onDidDelete(() => tree.refresh());
|
||||||
context.subscriptions.push(watcher);
|
context.subscriptions.push(watcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,41 +36,13 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
statusBarItem.show();
|
statusBarItem.show();
|
||||||
context.subscriptions.push(statusBarItem);
|
context.subscriptions.push(statusBarItem);
|
||||||
|
|
||||||
const createTaskCommand = vscode.commands.registerCommand(
|
registerTaskCommands(context, { repo, config, tree });
|
||||||
"projectTasks.createTask",
|
|
||||||
async () => {
|
|
||||||
const title = await vscode.window.showInputBox({
|
|
||||||
prompt: "Task title",
|
|
||||||
placeHolder: "Enter task title...",
|
|
||||||
});
|
|
||||||
if (!title) return;
|
|
||||||
|
|
||||||
const projectPath = configStore.getProjectTaskPath();
|
context.subscriptions.push(
|
||||||
if (!projectPath) {
|
vscode.commands.registerCommand("projectTasks.openDashboard", () => {
|
||||||
vscode.window.showErrorMessage("Open a workspace folder first");
|
void vscode.window.showInformationMessage("Dashboard coming soon!");
|
||||||
return;
|
}),
|
||||||
}
|
|
||||||
|
|
||||||
await taskStore.create(projectPath, {
|
|
||||||
title,
|
|
||||||
description: "",
|
|
||||||
status: "todo",
|
|
||||||
priority: "medium",
|
|
||||||
tags: [],
|
|
||||||
assignee: "",
|
|
||||||
});
|
|
||||||
vscode.window.showInformationMessage(`Task created: ${title}`);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const openDashboardCommand = vscode.commands.registerCommand(
|
|
||||||
"projectTasks.openDashboard",
|
|
||||||
() => {
|
|
||||||
vscode.window.showInformationMessage("Dashboard coming soon!");
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
context.subscriptions.push(createTaskCommand, openDashboardCommand);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate() {}
|
export function deactivate() {}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import * as vscode from "vscode";
|
||||||
|
import { Task } from "@/model/task";
|
||||||
|
|
||||||
|
/** Tree item that carries the domain task (for context-menu commands). */
|
||||||
|
export class TaskTreeItem extends vscode.TreeItem {
|
||||||
|
readonly task: Task;
|
||||||
|
|
||||||
|
constructor(task: Task) {
|
||||||
|
super(task.title, vscode.TreeItemCollapsibleState.None);
|
||||||
|
this.task = task;
|
||||||
|
this.id = task.id;
|
||||||
|
this.description = task.priority;
|
||||||
|
this.contextValue = "task";
|
||||||
|
this.tooltip = new vscode.MarkdownString(
|
||||||
|
`**${task.title}**\n\nPriority: ${task.priority}\nStatus: ${task.status}${
|
||||||
|
task.assignee ? `\nAssignee: ${task.assignee}` : ""
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
this.command = {
|
||||||
|
command: "projectTasks.openTask",
|
||||||
|
title: "Open Task",
|
||||||
|
arguments: [this],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import * as vscode from "vscode";
|
||||||
import { Task, TaskStatus } from "@/model/task";
|
import { Task, TaskStatus } from "@/model/task";
|
||||||
import { IConfigProvider, ITaskRepository } from "@/ports";
|
import { IConfigProvider, ITaskRepository } from "@/ports";
|
||||||
import * as vscode from "vscode";
|
import { TASK_STATUS_LABELS, TASK_STATUS_ORDER } from "@/ui/taskLabels";
|
||||||
|
import { TaskTreeItem } from "./taskTreeItem";
|
||||||
const STATUS_ORDER: TaskStatus[] = ["todo", "in-progress", "done", "cancelled"];
|
|
||||||
|
|
||||||
const STATUS_ICONS: Record<TaskStatus, vscode.ThemeIcon> = {
|
const STATUS_ICONS: Record<TaskStatus, vscode.ThemeIcon> = {
|
||||||
todo: new vscode.ThemeIcon("circle-outline"),
|
todo: new vscode.ThemeIcon("circle-outline"),
|
||||||
@@ -14,11 +14,16 @@ const STATUS_ICONS: Record<TaskStatus, vscode.ThemeIcon> = {
|
|||||||
class TaskGroupTreeItem extends vscode.TreeItem {
|
class TaskGroupTreeItem extends vscode.TreeItem {
|
||||||
readonly tasks: Task[];
|
readonly tasks: Task[];
|
||||||
|
|
||||||
constructor(label: string, tasks: Task[], icon: vscode.ThemeIcon) {
|
constructor(status: TaskStatus, tasks: Task[]) {
|
||||||
super(label, vscode.TreeItemCollapsibleState.Collapsed);
|
super(
|
||||||
this.iconPath = icon;
|
TASK_STATUS_LABELS[status],
|
||||||
|
vscode.TreeItemCollapsibleState.Collapsed,
|
||||||
|
);
|
||||||
|
this.iconPath = STATUS_ICONS[status];
|
||||||
this.tasks = tasks;
|
this.tasks = tasks;
|
||||||
this.description = `(${tasks.length})`;
|
this.description = `(${tasks.length})`;
|
||||||
|
this.id = `group-${status}`;
|
||||||
|
this.contextValue = "taskGroup";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +53,10 @@ export class TaskTreeProvider implements vscode.TreeDataProvider<vscode.TreeItem
|
|||||||
if (!element) {
|
if (!element) {
|
||||||
return this.getRootChildren();
|
return this.getRootChildren();
|
||||||
}
|
}
|
||||||
return this.getTaskChildren(element as TaskGroupTreeItem);
|
if (element instanceof TaskGroupTreeItem) {
|
||||||
|
return element.tasks.map((task) => new TaskTreeItem(task));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getRootChildren(): Promise<vscode.TreeItem[]> {
|
private async getRootChildren(): Promise<vscode.TreeItem[]> {
|
||||||
@@ -59,37 +67,9 @@ export class TaskTreeProvider implements vscode.TreeDataProvider<vscode.TreeItem
|
|||||||
|
|
||||||
const tasks = await this.#taskStore.list(projectPath);
|
const tasks = await this.#taskStore.list(projectPath);
|
||||||
|
|
||||||
return STATUS_ORDER.map((status) => {
|
return TASK_STATUS_ORDER.map((status) => {
|
||||||
const groupTasks = tasks.filter((t) => t.status === status);
|
const groupTasks = tasks.filter((t) => t.status === status);
|
||||||
const label =
|
return new TaskGroupTreeItem(status, groupTasks);
|
||||||
status === "todo"
|
|
||||||
? "To Do"
|
|
||||||
: status === "in-progress"
|
|
||||||
? "In Progress"
|
|
||||||
: status === "done"
|
|
||||||
? "Done"
|
|
||||||
: "Cancelled";
|
|
||||||
const item = new TaskGroupTreeItem(
|
|
||||||
label,
|
|
||||||
groupTasks,
|
|
||||||
STATUS_ICONS[status],
|
|
||||||
);
|
|
||||||
item.id = `group-${status}`;
|
|
||||||
item.contextValue = "taskGroup";
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTaskChildren(group: TaskGroupTreeItem): vscode.TreeItem[] {
|
|
||||||
return group.tasks.map((task) => {
|
|
||||||
const item = new vscode.TreeItem(task.title);
|
|
||||||
item.id = task.id;
|
|
||||||
item.description = task.priority;
|
|
||||||
item.tooltip = new vscode.MarkdownString(
|
|
||||||
`**${task.title}**\n\nPriority: ${task.priority}\nStatus: ${task.status}${task.assignee ? `\nAssignee: ${task.assignee}` : ""}`,
|
|
||||||
);
|
|
||||||
item.contextValue = "task";
|
|
||||||
return item;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user