feat: add global tasks folder
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
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,52 @@
|
||||
import * as vscode from "vscode";
|
||||
import { listLocatedTasks } from "@/commands/listLocatedTasks";
|
||||
import { resolveTaskLocations } from "@/commands/resolveTaskLocations";
|
||||
import { TaskLocation } from "@/model/taskLocation";
|
||||
import { IConfigProvider, ITaskRepository } from "@/ports";
|
||||
import { TaskTreeItem } from "@/views/taskTreeItem";
|
||||
import { TASK_SCOPE_LABELS, TASK_STATUS_LABELS } from "./taskLabels";
|
||||
|
||||
export type TaskRef = {
|
||||
id: string;
|
||||
location: TaskLocation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve task id + location from a TreeView item, or QuickPick when
|
||||
* the command is invoked from the Command Palette.
|
||||
*/
|
||||
export async function resolveTaskRef(
|
||||
repo: ITaskRepository,
|
||||
config: IConfigProvider,
|
||||
item?: unknown,
|
||||
): Promise<TaskRef | undefined> {
|
||||
if (item instanceof TaskTreeItem) {
|
||||
return { id: item.task.id, location: item.location };
|
||||
}
|
||||
|
||||
const locations = resolveTaskLocations(config);
|
||||
if (locations.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = await listLocatedTasks(repo, locations);
|
||||
if (result.isErr() || result.value.length === 0) {
|
||||
void vscode.window.showInformationMessage("No tasks found");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const picked = await vscode.window.showQuickPick(
|
||||
result.value.map((located) => ({
|
||||
label: located.task.title,
|
||||
description: `${TASK_SCOPE_LABELS[located.location.scope]} · ${TASK_STATUS_LABELS[located.task.status]}`,
|
||||
detail: located.task.priority,
|
||||
ref: {
|
||||
id: located.task.id,
|
||||
location: located.location,
|
||||
} satisfies TaskRef,
|
||||
})),
|
||||
{ placeHolder: "Select a task" },
|
||||
);
|
||||
|
||||
return picked?.ref;
|
||||
}
|
||||
+63
-37
@@ -3,13 +3,15 @@ import { changeStatus } from "@/commands/changeStatus";
|
||||
import { createTask } from "@/commands/createTask";
|
||||
import { deleteTask } from "@/commands/deleteTask";
|
||||
import { openTask } from "@/commands/openTask";
|
||||
import { resolveTaskLocations } from "@/commands/resolveTaskLocations";
|
||||
import { appError, AppErrorVariant } from "@/error";
|
||||
import { TaskLocation } from "@/model/taskLocation";
|
||||
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";
|
||||
import { resolveTaskRef } from "./resolveTaskRef";
|
||||
import { TASK_SCOPE_LABELS, TASK_STATUS_LABELS, TASK_STATUS_ORDER } from "./taskLabels";
|
||||
|
||||
export type TaskCommandDeps = {
|
||||
repo: ITaskRepository;
|
||||
@@ -24,18 +26,45 @@ function notifyMutated(deps: TaskCommandDeps): void {
|
||||
deps.onTasksMutated?.();
|
||||
}
|
||||
|
||||
async function pickCreateLocation(
|
||||
config: IConfigProvider,
|
||||
): Promise<TaskLocation | undefined> {
|
||||
const locations = resolveTaskLocations(config);
|
||||
if (locations.length === 0) {
|
||||
presentError(appError(AppErrorVariant.NO_FOLDER));
|
||||
return undefined;
|
||||
}
|
||||
if (locations.length === 1) {
|
||||
return locations[0];
|
||||
}
|
||||
|
||||
const picked = await vscode.window.showQuickPick(
|
||||
locations.map((location) => ({
|
||||
label: TASK_SCOPE_LABELS[location.scope],
|
||||
description: location.folderPath,
|
||||
location,
|
||||
})),
|
||||
{ placeHolder: "Create task in…" },
|
||||
);
|
||||
return picked?.location;
|
||||
}
|
||||
|
||||
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 location = await pickCreateLocation(deps.config);
|
||||
if (location === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await createTask(deps.repo, {
|
||||
folderPath: deps.config.getProjectTaskPath(),
|
||||
folderPath: location.folderPath,
|
||||
title,
|
||||
});
|
||||
|
||||
@@ -46,7 +75,7 @@ export async function runCreateTask(deps: TaskCommandDeps): Promise<void> {
|
||||
|
||||
notifyMutated(deps);
|
||||
void vscode.window.showInformationMessage(
|
||||
`Task created: ${result.value.title}`,
|
||||
`Task created (${TASK_SCOPE_LABELS[location.scope]}): ${result.value.title}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,18 +83,18 @@ export async function runOpenTask(
|
||||
deps: TaskCommandDeps,
|
||||
item?: unknown,
|
||||
): Promise<void> {
|
||||
const folderPath = deps.config.getProjectTaskPath();
|
||||
if (folderPath === undefined) {
|
||||
presentError(appError(AppErrorVariant.NO_FOLDER));
|
||||
const ref = await resolveTaskRef(deps.repo, deps.config, item);
|
||||
if (ref === undefined) {
|
||||
if (resolveTaskLocations(deps.config).length === 0) {
|
||||
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 });
|
||||
const result = await openTask(deps.repo, {
|
||||
folderPath: ref.location.folderPath,
|
||||
id: ref.id,
|
||||
});
|
||||
if (result.isErr()) {
|
||||
presentError(result.error);
|
||||
return;
|
||||
@@ -79,14 +108,11 @@ 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) {
|
||||
const ref = await resolveTaskRef(deps.repo, deps.config, item);
|
||||
if (ref === undefined) {
|
||||
if (resolveTaskLocations(deps.config).length === 0) {
|
||||
presentError(appError(AppErrorVariant.NO_FOLDER));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -99,7 +125,10 @@ export async function runDeleteTask(
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await deleteTask(deps.repo, { folderPath, id });
|
||||
const result = await deleteTask(deps.repo, {
|
||||
folderPath: ref.location.folderPath,
|
||||
id: ref.id,
|
||||
});
|
||||
if (result.isErr()) {
|
||||
presentError(result.error);
|
||||
return;
|
||||
@@ -113,14 +142,11 @@ 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) {
|
||||
const ref = await resolveTaskRef(deps.repo, deps.config, item);
|
||||
if (ref === undefined) {
|
||||
if (resolveTaskLocations(deps.config).length === 0) {
|
||||
presentError(appError(AppErrorVariant.NO_FOLDER));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,8 +162,8 @@ export async function runChangeStatus(
|
||||
}
|
||||
|
||||
const result = await changeStatus(deps.repo, {
|
||||
folderPath,
|
||||
id,
|
||||
folderPath: ref.location.folderPath,
|
||||
id: ref.id,
|
||||
status: picked.status,
|
||||
});
|
||||
if (result.isErr()) {
|
||||
@@ -157,19 +183,19 @@ export function registerTaskCommands(
|
||||
deps: TaskCommandDeps,
|
||||
): void {
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("projectTasks.createTask", () =>
|
||||
vscode.commands.registerCommand("xboctFlow.createTask", () =>
|
||||
runCreateTask(deps),
|
||||
),
|
||||
vscode.commands.registerCommand(
|
||||
"projectTasks.openTask",
|
||||
"xboctFlow.openTask",
|
||||
(item?: unknown) => runOpenTask(deps, item),
|
||||
),
|
||||
vscode.commands.registerCommand(
|
||||
"projectTasks.deleteTask",
|
||||
"xboctFlow.deleteTask",
|
||||
(item?: unknown) => runDeleteTask(deps, item),
|
||||
),
|
||||
vscode.commands.registerCommand(
|
||||
"projectTasks.changeStatus",
|
||||
"xboctFlow.changeStatus",
|
||||
(item?: unknown) => runChangeStatus(deps, item),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TaskStatus } from "@/model/task";
|
||||
import { TaskScope } from "@/model/taskLocation";
|
||||
|
||||
export const TASK_STATUS_ORDER: TaskStatus[] = [
|
||||
"todo",
|
||||
@@ -13,3 +14,9 @@ export const TASK_STATUS_LABELS: Record<TaskStatus, string> = {
|
||||
done: "Done",
|
||||
cancelled: "Cancelled",
|
||||
};
|
||||
|
||||
export const TASK_SCOPE_LABELS: Record<TaskScope, string> = {
|
||||
project: "Project",
|
||||
global: "Global",
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user