From 0454a32d8c5de230925e008e7c4b30b0c8f47986 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Wed, 15 Jul 2026 00:51:58 +0500 Subject: [PATCH] feat: add buttons - open task in dashboard, open dashboard --- package.json | 42 ++++++++++++++++++++++++++++++--- src/dashboard/messages.test.ts | 6 +++++ src/dashboard/messages.ts | 4 +++- src/extension.ts | 10 -------- src/ui/taskCommands.ts | 35 +++++++++++++++++++++++++++ src/views/taskDashboardHtml.ts | 8 ++++++- src/views/taskDashboardPanel.ts | 38 ++++++++++++++++++++++++----- todo.md | 13 ++++++---- 8 files changed, 130 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index d21cf72..75347d0 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,13 @@ }, { "command": "xboctFlow.openDashboard", - "title": "XBOCT flow: Open Dashboard" + "title": "XBOCT flow: Open Dashboard", + "icon": "$(dashboard)" + }, + { + "command": "xboctFlow.openInDashboard", + "title": "XBOCT flow: Edit in Dashboard", + "icon": "$(edit)" } ], "configuration": { @@ -98,7 +104,12 @@ { "command": "xboctFlow.createTask", "when": "view == xboctFlow.tasks", - "group": "navigation" + "group": "navigation@1" + }, + { + "command": "xboctFlow.openDashboard", + "when": "view == xboctFlow.tasks", + "group": "navigation@2" } ], "view/item/context": [ @@ -107,15 +118,40 @@ "when": "view == xboctFlow.tasks && viewItem == task", "group": "inline@1" }, + { + "command": "xboctFlow.openInDashboard", + "when": "view == xboctFlow.tasks && viewItem == task", + "group": "inline@2" + }, { "command": "xboctFlow.changeStatus", "when": "view == xboctFlow.tasks && viewItem == task", - "group": "1_task@1" + "group": "inline@3" }, { "command": "xboctFlow.deleteTask", "when": "view == xboctFlow.tasks && viewItem == task", + "group": "inline@4" + }, + { + "command": "xboctFlow.openTask", + "when": "view == xboctFlow.tasks && viewItem == task", + "group": "1_task@1" + }, + { + "command": "xboctFlow.openInDashboard", + "when": "view == xboctFlow.tasks && viewItem == task", "group": "1_task@2" + }, + { + "command": "xboctFlow.changeStatus", + "when": "view == xboctFlow.tasks && viewItem == task", + "group": "1_task@3" + }, + { + "command": "xboctFlow.deleteTask", + "when": "view == xboctFlow.tasks && viewItem == task", + "group": "1_task@4" } ] } diff --git a/src/dashboard/messages.test.ts b/src/dashboard/messages.test.ts index 1f8bec9..6a0f2f1 100644 --- a/src/dashboard/messages.test.ts +++ b/src/dashboard/messages.test.ts @@ -60,6 +60,12 @@ describe("parseDashboardInboundMessage", () => { ); }); + it("parses createTask", () => { + expect(parseDashboardInboundMessage({ type: "createTask" })).toEqual( + ok({ type: "createTask" } satisfies DashboardInboundMessage), + ); + }); + it("rejects non-objects", () => { const result = parseDashboardInboundMessage(null); expect(result.isErr()).toBe(true); diff --git a/src/dashboard/messages.ts b/src/dashboard/messages.ts index 69af3c7..b38d804 100644 --- a/src/dashboard/messages.ts +++ b/src/dashboard/messages.ts @@ -9,7 +9,8 @@ export type DashboardInboundMessage = | { type: "setFilter"; filter: TaskFilter } | { type: "setGroupBy"; groupBy: GroupBy } | { type: "saveDescription"; id: string; description: string } - | { type: "refresh" }; + | { type: "refresh" } + | { type: "createTask" }; const GROUP_BY_VALUES: ReadonlySet = new Set([ "none", @@ -44,6 +45,7 @@ export function parseDashboardInboundMessage( switch (type) { case "ready": case "refresh": + case "createTask": return ok({ type }); case "selectTask": { diff --git a/src/extension.ts b/src/extension.ts index aae72e0..d72ddb2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -50,16 +50,6 @@ export function activate(context: vscode.ExtensionContext) { tree, onTasksMutated: () => TaskDashboardPanel.refreshIfOpen(), }); - - context.subscriptions.push( - vscode.commands.registerCommand("xboctFlow.openDashboard", () => { - TaskDashboardPanel.show({ - repo, - config, - onTasksMutated: () => tree.refresh(), - }); - }), - ); } export function deactivate() {} diff --git a/src/ui/taskCommands.ts b/src/ui/taskCommands.ts index a79cdb6..d06af1b 100644 --- a/src/ui/taskCommands.ts +++ b/src/ui/taskCommands.ts @@ -8,6 +8,7 @@ import { appError, AppErrorVariant } from "@/error"; import { TaskLocation } from "@/model/taskLocation"; import { TaskStatus } from "@/model/task"; import { IConfigProvider, ITaskRepository } from "@/ports"; +import { TaskDashboardPanel } from "@/views/taskDashboardPanel"; import { TaskTreeProvider } from "@/views/taskTreeProvider"; import { presentError } from "./presentError"; import { resolveTaskRef } from "./resolveTaskRef"; @@ -21,6 +22,14 @@ export type TaskCommandDeps = { onTasksMutated?: () => void; }; +function dashboardDeps(deps: TaskCommandDeps) { + return { + repo: deps.repo, + config: deps.config, + onTasksMutated: () => deps.tree.refresh(), + }; +} + function notifyMutated(deps: TaskCommandDeps): void { deps.tree.refresh(); deps.onTasksMutated?.(); @@ -177,6 +186,25 @@ export async function runChangeStatus( ); } +export async function runOpenInDashboard( + deps: TaskCommandDeps, + item?: unknown, +): Promise { + const ref = await resolveTaskRef(deps.repo, deps.config, item); + if (ref === undefined) { + if (resolveTaskLocations(deps.config).length === 0) { + presentError(appError(AppErrorVariant.NO_FOLDER)); + } + return; + } + + TaskDashboardPanel.show(dashboardDeps(deps), { selectedId: ref.id }); +} + +export function runOpenDashboard(deps: TaskCommandDeps): void { + TaskDashboardPanel.show(dashboardDeps(deps)); +} + /** Register all task command adapters on the extension host. */ export function registerTaskCommands( context: vscode.ExtensionContext, @@ -198,5 +226,12 @@ export function registerTaskCommands( "xboctFlow.changeStatus", (item?: unknown) => runChangeStatus(deps, item), ), + vscode.commands.registerCommand("xboctFlow.openDashboard", () => + runOpenDashboard(deps), + ), + vscode.commands.registerCommand( + "xboctFlow.openInDashboard", + (item?: unknown) => runOpenInDashboard(deps, item), + ), ); } diff --git a/src/views/taskDashboardHtml.ts b/src/views/taskDashboardHtml.ts index 121d324..2585fcd 100644 --- a/src/views/taskDashboardHtml.ts +++ b/src/views/taskDashboardHtml.ts @@ -199,6 +199,7 @@ export function getTaskDashboardHtml( + @@ -208,7 +209,7 @@ export function getTaskDashboardHtml(
-
Select a task or create one from the sidebar.
+
Select a task or create one with «+ Task».

@@ -251,6 +252,7 @@ export function getTaskDashboardHtml( query: document.getElementById("query"), groupBy: document.getElementById("groupBy"), refresh: document.getElementById("refresh"), + createTask: document.getElementById("createTask"), resetFilters: document.getElementById("resetFilters"), statusFilters: document.getElementById("status-filters"), priorityFilters: document.getElementById("priority-filters"), @@ -437,6 +439,10 @@ export function getTaskDashboardHtml( post({ type: "refresh" }); }); + el.createTask.addEventListener("click", () => { + post({ type: "createTask" }); + }); + el.resetFilters.addEventListener("click", () => { resetFilters(); }); diff --git a/src/views/taskDashboardPanel.ts b/src/views/taskDashboardPanel.ts index 386c788..6f9960e 100644 --- a/src/views/taskDashboardPanel.ts +++ b/src/views/taskDashboardPanel.ts @@ -23,6 +23,11 @@ export type TaskDashboardDeps = { onTasksMutated?: () => void; }; +export type TaskDashboardShowOptions = { + /** Pre-select this task after load (e.g. open from tree). */ + selectedId?: string; +}; + /** * Host adapter for the Task Dashboard webview. * Owns panel lifecycle and message wiring; business rules stay in use-cases. @@ -30,16 +35,23 @@ export type TaskDashboardDeps = { export class TaskDashboardPanel { static #current: TaskDashboardPanel | undefined; - static show(deps: TaskDashboardDeps): void { + static show( + deps: TaskDashboardDeps, + options: TaskDashboardShowOptions = {}, + ): void { if (TaskDashboardPanel.#current) { - TaskDashboardPanel.#current.#panel.reveal(vscode.ViewColumn.One); - void TaskDashboardPanel.#current.reloadTasks(); + const current = TaskDashboardPanel.#current; + if (options.selectedId !== undefined) { + current.#selectedId = options.selectedId; + } + current.#panel.reveal(vscode.ViewColumn.One); + void current.reloadTasks(); return; } const panel = vscode.window.createWebviewPanel( "xboctFlow.dashboard", - "Task Dashboard", + "XBOCT flow", vscode.ViewColumn.One, { enableScripts: true, @@ -47,7 +59,11 @@ export class TaskDashboardPanel { }, ); - TaskDashboardPanel.#current = new TaskDashboardPanel(panel, deps); + TaskDashboardPanel.#current = new TaskDashboardPanel( + panel, + deps, + options.selectedId, + ); } static refreshIfOpen(): void { @@ -63,9 +79,14 @@ export class TaskDashboardPanel { #selectedId: string | undefined; #disposed = false; - constructor(panel: vscode.WebviewPanel, deps: TaskDashboardDeps) { + constructor( + panel: vscode.WebviewPanel, + deps: TaskDashboardDeps, + selectedId?: string, + ) { this.#panel = panel; this.#deps = deps; + this.#selectedId = selectedId; const nonce = createNonce(); this.#panel.webview.html = getTaskDashboardHtml(this.#panel.webview, nonce); @@ -140,6 +161,11 @@ export class TaskDashboardPanel { case "saveDescription": await this.#saveDescription(message.id, message.description); return; + + case "createTask": + await vscode.commands.executeCommand("xboctFlow.createTask"); + await this.reloadTasks(); + return; } } diff --git a/todo.md b/todo.md index ff41ab7..833d00e 100644 --- a/todo.md +++ b/todo.md @@ -45,11 +45,14 @@ headless/тестов можно оставить; editor-save — optional path ломать format-on-save историю: raw-save тоже лучше через editor path, если нужен паритет с VS Code. -[ ] **Боковая панель** В боковой панели должны быть +[x] **Боковая панель** -- кнопка открытия дешборда -- кнопки для - удаления, редактирования через дешборд +- кнопка открытия dashboard (view title → `xboctFlow.openDashboard`) +- кнопки на задаче (inline): open file, edit in dashboard + (`xboctFlow.openInDashboard` + preselect), delete +- change status остаётся в context menu -[ ] **Dashboard** +[x] **Dashboard: кнопка создания задачи** -- кнопка создания задачи +- `+ Task` в toolbar → inbound `createTask` → host + `executeCommand("xboctFlow.createTask")` → reload list