Files
xboct-flow/src/views/taskTreeProvider.ts
T

136 lines
3.9 KiB
TypeScript

import { listLocatedTasks } from "@/commands/listLocatedTasks";
import { resolveTaskLocations } from "@/commands/resolveTaskLocations";
import { Task } from "@/model/task";
import { LocatedTask, TaskLocation } from "@/model/taskLocation";
import {
TaskStatus,
TASK_STATUS_META,
TASK_STATUS_ORDER,
} from "@/model/taskStatus";
import { IConfigProvider, ITaskRepository } from "@/ports";
import { TASK_SCOPE_LABELS, TASK_STATUS_LABELS } from "@/ui/taskLabels";
import * as vscode from "vscode";
import { TaskTreeItem } from "./taskTreeItem";
class ScopeTreeItem extends vscode.TreeItem {
readonly location: TaskLocation;
readonly locatedTasks: LocatedTask[];
constructor(location: TaskLocation, locatedTasks: LocatedTask[]) {
super(
TASK_SCOPE_LABELS[location.scope],
vscode.TreeItemCollapsibleState.Expanded,
);
this.location = location;
this.locatedTasks = locatedTasks;
this.id = `scope-${location.scope}`;
this.contextValue = "taskScope";
this.description = `(${locatedTasks.length})`;
this.iconPath =
location.scope === "project"
? new vscode.ThemeIcon("root-folder")
: new vscode.ThemeIcon("globe");
}
}
class TaskGroupTreeItem extends vscode.TreeItem {
readonly location: TaskLocation;
readonly tasks: Task[];
constructor(status: TaskStatus, location: TaskLocation, tasks: Task[]) {
super(
TASK_STATUS_LABELS[status],
vscode.TreeItemCollapsibleState.Collapsed,
);
this.location = location;
this.tasks = tasks;
this.description = `(${tasks.length})`;
this.id = `group-${location.scope}-${status}`;
this.contextValue = "taskGroup";
this.iconPath = new vscode.ThemeIcon(
TASK_STATUS_META[status].icon,
status === TaskStatus.UNKNOWN
? new vscode.ThemeColor("charts.red")
: undefined,
);
}
}
export class TaskTreeProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
#onDidChangeTreeData = new vscode.EventEmitter<
vscode.TreeItem | undefined | null | void
>();
readonly onDidChangeTreeData = this.#onDidChangeTreeData.event;
#taskStore: ITaskRepository;
#configStore: IConfigProvider;
constructor(taskStore: ITaskRepository, configStore: IConfigProvider) {
this.#taskStore = taskStore;
this.#configStore = configStore;
}
refresh(): void {
this.#onDidChangeTreeData.fire();
}
getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
return element;
}
async getChildren(element?: vscode.TreeItem): Promise<vscode.TreeItem[]> {
if (!element) {
return this.getRootChildren();
}
if (element instanceof ScopeTreeItem) {
return this.getStatusGroups(element);
}
if (element instanceof TaskGroupTreeItem) {
return element.tasks.map(
(task) => new TaskTreeItem(task, element.location),
);
}
return [];
}
private async getRootChildren(): Promise<vscode.TreeItem[]> {
const locations = resolveTaskLocations(this.#configStore);
if (locations.length === 0) {
return [
new vscode.TreeItem(
"Configure project or global task path in settings",
),
];
}
const result = await listLocatedTasks(this.#taskStore, locations);
if (result.isErr()) {
return [new vscode.TreeItem(result.error.message)];
}
const byScope = new Map<string, LocatedTask[]>();
for (const location of locations) {
byScope.set(location.scope, []);
}
for (const located of result.value) {
byScope.get(located.location.scope)?.push(located);
}
return locations.map((location) => {
const tasks = byScope.get(location.scope) ?? [];
return new ScopeTreeItem(location, tasks);
});
}
private getStatusGroups(scope: ScopeTreeItem): vscode.TreeItem[] {
const tasks = scope.locatedTasks.map((item) => item.task);
return TASK_STATUS_ORDER.filter((status) => {
const groupTasks = tasks.filter((t) => t.status === status);
return !(groupTasks.length === 0 && TASK_STATUS_META[status].fallback);
}).map((status) => {
const groupTasks = tasks.filter((t) => t.status === status);
return new TaskGroupTreeItem(status, scope.location, groupTasks);
});
}
}