feat: update kanban view, dashboard view, add backlog status
This commit is contained in:
+109
-14
@@ -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, """);
|
||||
}
|
||||
|
||||
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>`;
|
||||
|
||||
Reference in New Issue
Block a user