feat: update kanban view, dashboard view, add backlog status

This commit is contained in:
2026-07-15 03:14:03 +05:00
parent 84f64af62d
commit e46437a512
16 changed files with 451 additions and 141 deletions
+109 -14
View File
@@ -1,4 +1,6 @@
import * as vscode from "vscode";
import { TaskScope } from "@/model/taskLocation";
import { TASK_SCOPE_LABELS } from "@/ui/taskLabels";
const NONCE_LENGTH = 32;
@@ -13,6 +15,11 @@ export function getTaskKanbanHtml(
`script-src 'nonce-${nonce}'`,
].join("; ");
const scopeProject = JSON.stringify(TaskScope.PROJECT);
const scopeGlobal = JSON.stringify(TaskScope.GLOBAL);
const labelProject = JSON.stringify(TASK_SCOPE_LABELS[TaskScope.PROJECT]);
const labelGlobal = JSON.stringify(TASK_SCOPE_LABELS[TaskScope.GLOBAL]);
return `<!DOCTYPE html>
<html lang="en">
<head>
@@ -33,6 +40,7 @@ export function getTaskKanbanHtml(
}
header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
padding: 8px 12px;
@@ -42,21 +50,32 @@ export function getTaskKanbanHtml(
margin: 0;
font-size: 1em;
font-weight: 600;
flex: 1;
}
button {
header .spacer { flex: 1; min-width: 8px; }
select, button {
font: inherit;
cursor: pointer;
padding: 4px 10px;
color: var(--vscode-input-foreground);
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border, transparent);
border-radius: 2px;
}
button {
color: var(--vscode-button-foreground);
background: var(--vscode-button-background);
border: none;
border-radius: 2px;
border-color: transparent;
}
button.secondary {
background: var(--vscode-button-secondaryBackground, var(--vscode-input-background));
color: var(--vscode-button-secondaryForeground, var(--vscode-foreground));
}
button.column-add {
padding: 0 6px;
font-size: 1em;
line-height: 1.2;
min-width: 24px;
}
#status-line {
font-size: 0.85em;
opacity: 0.85;
@@ -65,10 +84,10 @@ export function getTaskKanbanHtml(
#status-line.error { color: var(--vscode-errorForeground); }
#board {
display: grid;
grid-template-columns: repeat(4, minmax(160px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 10px;
padding: 10px;
height: calc(100% - 48px);
height: calc(100% - 52px);
overflow: auto;
align-items: stretch;
}
@@ -89,9 +108,11 @@ export function getTaskKanbanHtml(
font-size: 0.9em;
border-bottom: 1px solid var(--vscode-panel-border, var(--vscode-widget-border));
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.column-header .title { flex: 1; }
.column-header .count {
opacity: 0.7;
font-weight: 400;
@@ -124,17 +145,31 @@ export function getTaskKanbanHtml(
<body>
<header>
<h1>Kanban</h1>
<label for="scopeFilter">Scope</label>
<select id="scopeFilter" title="Project / Global filter"></select>
<button id="toggleHidden" type="button" class="secondary" title="Show or hide backlog and cancelled">Show hidden</button>
<span class="spacer"></span>
<span id="status-line"></span>
<button id="refresh" type="button" class="secondary" title="Refresh">↻</button>
</header>
<div id="board"></div>
<script nonce="${nonce}">
const vscode = acquireVsCodeApi();
const SCOPE_PROJECT = ${scopeProject};
const SCOPE_GLOBAL = ${scopeGlobal};
const LABEL_PROJECT = ${labelProject};
const LABEL_GLOBAL = ${labelGlobal};
const boardEl = document.getElementById("board");
const statusLine = document.getElementById("status-line");
const refreshBtn = document.getElementById("refresh");
const scopeFilterEl = document.getElementById("scopeFilter");
const toggleHiddenBtn = document.getElementById("toggleHidden");
let board = { columns: [] };
let scopeFilter = "all";
let showHidden = false;
let availableScopes = [];
let dragTaskId = null;
function post(message) {
@@ -154,7 +189,38 @@ export function getTaskKanbanHtml(
.replace(/"/g, "&quot;");
}
function scopeLabel(scope) {
if (scope === SCOPE_PROJECT) return LABEL_PROJECT;
if (scope === SCOPE_GLOBAL) return LABEL_GLOBAL;
return scope;
}
function renderScopeFilter() {
const options = [{ value: "all", label: "All" }];
for (const scope of availableScopes) {
options.push({ value: scope, label: scopeLabel(scope) });
}
scopeFilterEl.innerHTML = options
.map(
(opt) =>
'<option value="' +
escapeHtml(opt.value) +
'"' +
(opt.value === scopeFilter ? " selected" : "") +
">" +
escapeHtml(opt.label) +
"</option>",
)
.join("");
}
function renderToggleHidden() {
toggleHiddenBtn.textContent = showHidden ? "Hide backlog/cancelled" : "Show hidden";
}
function render() {
renderScopeFilter();
renderToggleHidden();
boardEl.innerHTML = "";
for (const column of board.columns || []) {
const col = document.createElement("section");
@@ -163,9 +229,27 @@ export function getTaskKanbanHtml(
const header = document.createElement("div");
header.className = "column-header";
header.innerHTML =
"<span>" + escapeHtml(column.label) + "</span>" +
'<span class="count">' + column.tasks.length + "</span>";
const title = document.createElement("span");
title.className = "title";
title.textContent = column.label;
const count = document.createElement("span");
count.className = "count";
count.textContent = String(column.tasks.length);
const addBtn = document.createElement("button");
addBtn.type = "button";
addBtn.className = "column-add secondary";
addBtn.title = "Create task in " + column.label;
addBtn.textContent = "+";
addBtn.addEventListener("click", () => {
post({ variant: "createTask", status: column.status });
});
header.appendChild(title);
header.appendChild(count);
header.appendChild(addBtn);
col.appendChild(header);
const body = document.createElement("div");
@@ -185,7 +269,7 @@ export function getTaskKanbanHtml(
const id = event.dataTransfer.getData("text/task-id") || dragTaskId;
const status = column.status;
if (!id) return;
post({ type: "moveTask", id: id, status: status });
post({ variant: "moveTask", id: id, status: status });
dragTaskId = null;
});
@@ -217,22 +301,33 @@ export function getTaskKanbanHtml(
}
refreshBtn.addEventListener("click", () => {
post({ type: "refresh" });
post({ variant: "refresh" });
});
scopeFilterEl.addEventListener("change", () => {
post({ variant: "setScopeFilter", scope: scopeFilterEl.value });
});
toggleHiddenBtn.addEventListener("click", () => {
post({ variant: "setShowHidden", showHidden: !showHidden });
});
window.addEventListener("message", (event) => {
const message = event.data;
if (!message || typeof message !== "object") return;
if (message.type === "state") {
if (message.variant === "state") {
board = message.board || { columns: [] };
scopeFilter = message.scopeFilter || "all";
showHidden = Boolean(message.showHidden);
availableScopes = message.availableScopes || [];
setStatus("");
render();
} else if (message.type === "error") {
} else if (message.variant === "error") {
setStatus((message.error && message.error.message) || "Error", true);
}
});
post({ type: "ready" });
post({ variant: "ready" });
</script>
</body>
</html>`;