Allow opening multiple burndown panels

This commit is contained in:
Gordon 2021-04-17 01:28:17 +01:00
parent 0d8508dd19
commit cb8647ba5a
8 changed files with 515 additions and 405 deletions

View File

@ -1,23 +1,24 @@
import * as path from 'path'; import * as path from "path";
import * as vscode from 'vscode'; import * as vscode from "vscode";
import KanbnTaskPanel from './KanbnTaskPanel'; import getNonce from "./getNonce";
import KanbnBurndownPanel from './KanbnBurndownPanel'; import KanbnTaskPanel from "./KanbnTaskPanel";
import KanbnBurndownPanel from "./KanbnBurndownPanel";
export default class KanbnBoardPanel { export default class KanbnBoardPanel {
public static currentPanel: KanbnBoardPanel | undefined; public static currentPanel: KanbnBoardPanel | undefined;
private static readonly viewType = 'react'; private static readonly viewType = "react";
private readonly _panel: vscode.WebviewPanel; private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: string; private readonly _extensionPath: string;
private readonly _workspacePath: string; private readonly _workspacePath: string;
private readonly _kanbn: typeof import('@basementuniverse/kanbn/src/main'); private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
private _disposables: vscode.Disposable[] = []; private _disposables: vscode.Disposable[] = [];
public static createOrShow( public static createOrShow(
extensionPath: string, extensionPath: string,
workspacePath: string, workspacePath: string,
kanbn: typeof import('@basementuniverse/kanbn/src/main') kanbn: typeof import("@basementuniverse/kanbn/src/main")
) { ) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
@ -45,23 +46,23 @@ export default class KanbnBoardPanel {
} }
let tasks: any[]; let tasks: any[];
try { try {
tasks = (await KanbnBoardPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map( tasks = (await KanbnBoardPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map((task) =>
task => KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task) KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task)
); );
} catch (error) { } catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error); vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return; return;
} }
KanbnBoardPanel.currentPanel._panel.webview.postMessage({ KanbnBoardPanel.currentPanel._panel.webview.postMessage({
type: 'index', type: "index",
index, index,
tasks, tasks,
hiddenColumns: index.options.hiddenColumns ?? [], hiddenColumns: index.options.hiddenColumns ?? [],
startedColumns: index.options.startedColumns ?? [], startedColumns: index.options.startedColumns ?? [],
completedColumns: index.options.completedColumns ?? [], completedColumns: index.options.completedColumns ?? [],
dateFormat: KanbnBoardPanel.currentPanel._kanbn.getDateFormat(index), dateFormat: KanbnBoardPanel.currentPanel._kanbn.getDateFormat(index),
showBurndownButton: vscode.workspace.getConfiguration('kanbn').get('showBurndownButton'), showBurndownButton: vscode.workspace.getConfiguration("kanbn").get("showBurndownButton"),
showSprintButton: vscode.workspace.getConfiguration('kanbn').get('showSprintButton') showSprintButton: vscode.workspace.getConfiguration("kanbn").get("showSprintButton"),
}); });
} }
} }
@ -70,14 +71,14 @@ export default class KanbnBoardPanel {
extensionPath: string, extensionPath: string,
workspacePath: string, workspacePath: string,
column: vscode.ViewColumn, column: vscode.ViewColumn,
kanbn: typeof import('@basementuniverse/kanbn/src/main') kanbn: typeof import("@basementuniverse/kanbn/src/main")
) { ) {
this._extensionPath = extensionPath; this._extensionPath = extensionPath;
this._workspacePath = workspacePath; this._workspacePath = workspacePath;
this._kanbn = kanbn; this._kanbn = kanbn;
// Create and show a new webview panel // Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(KanbnBoardPanel.viewType, 'Kanbn Board', column, { this._panel = vscode.window.createWebviewPanel(KanbnBoardPanel.viewType, "Kanbn Board", column, {
// Enable javascript in the webview // Enable javascript in the webview
enableScripts: true, enableScripts: true,
@ -86,18 +87,18 @@ export default class KanbnBoardPanel {
// Restrict the webview to only loading content from allowed paths // Restrict the webview to only loading content from allowed paths
localResourceRoots: [ localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, 'build')), vscode.Uri.file(path.join(this._extensionPath, "build")),
vscode.Uri.file(path.join(this._workspacePath, this._kanbn.getFolderName())), vscode.Uri.file(path.join(this._workspacePath, this._kanbn.getFolderName())),
vscode.Uri.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist')) vscode.Uri.file(path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
] ],
}); });
(this._panel as any).iconPath = { (this._panel as any).iconPath = {
light: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'project_light.svg')), light: vscode.Uri.file(path.join(this._extensionPath, "resources", "project_light.svg")),
dark: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'project_dark.svg')) dark: vscode.Uri.file(path.join(this._extensionPath, "resources", "project_dark.svg")),
}; };
// Set the webview's title to the kanbn project name // Set the webview's title to the kanbn project name
this._kanbn.getIndex().then(index => { this._kanbn.getIndex().then((index) => {
this._panel.title = index.name; this._panel.title = index.name;
}); });
@ -109,15 +110,15 @@ export default class KanbnBoardPanel {
this._panel.onDidDispose(() => this.dispose(), null, this._disposables); this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
// Handle messages from the webview // Handle messages from the webview
this._panel.webview.onDidReceiveMessage(async message => { this._panel.webview.onDidReceiveMessage(
async (message) => {
switch (message.command) { switch (message.command) {
// Display error message // Display error message
case 'error': case "error":
vscode.window.showErrorMessage(message.text); vscode.window.showErrorMessage(message.text);
return; return;
case 'kanbn.task': case "kanbn.task":
KanbnTaskPanel.show( KanbnTaskPanel.show(
this._extensionPath, this._extensionPath,
this._workspacePath, this._workspacePath,
@ -128,7 +129,7 @@ export default class KanbnBoardPanel {
return; return;
// Move a task // Move a task
case 'kanbn.move': case "kanbn.move":
try { try {
await kanbn.moveTask(message.task, message.columnName, message.position); await kanbn.moveTask(message.task, message.columnName, message.position);
} catch (e) { } catch (e) {
@ -137,45 +138,37 @@ export default class KanbnBoardPanel {
return; return;
// Create a task // Create a task
case 'kanbn.addTask': case "kanbn.addTask":
KanbnTaskPanel.show( KanbnTaskPanel.show(this._extensionPath, this._workspacePath, this._kanbn, null, message.columnName);
this._extensionPath,
this._workspacePath,
this._kanbn,
null,
message.columnName
);
return; return;
// Open a burndown chart // Open a burndown chart
case 'kanbn.burndown': case "kanbn.burndown":
KanbnBurndownPanel.createOrShow( KanbnBurndownPanel.show(this._extensionPath, this._workspacePath, this._kanbn);
this._extensionPath, KanbnBurndownPanel.updateAll();
this._workspacePath,
this._kanbn
);
KanbnBurndownPanel.update();
return; return;
// Start a new sprint // Start a new sprint
case 'kanbn.sprint': case "kanbn.sprint":
// Prompt for a sprint name // Prompt for a sprint name
const newSprintName = await vscode.window.showInputBox({ const newSprintName = await vscode.window.showInputBox({
placeHolder: 'The sprint name.' placeHolder: "The sprint name.",
}); });
// If the input prompt wasn't cancelled, start a new sprint // If the input prompt wasn't cancelled, start a new sprint
if (newSprintName !== undefined) { if (newSprintName !== undefined) {
try { try {
await kanbn.sprint(newSprintName, '', new Date()); await kanbn.sprint(newSprintName, "", new Date());
} catch (e) { } catch (e) {
vscode.window.showErrorMessage(e.message); vscode.window.showErrorMessage(e.message);
} }
} }
return; return;
} }
}, null, this._disposables); },
null,
this._disposables
);
} }
public dispose() { public dispose() {
@ -192,21 +185,21 @@ export default class KanbnBoardPanel {
} }
private _getHtmlForWebview() { private _getHtmlForWebview() {
const manifest = require(path.join(this._extensionPath, 'build', 'asset-manifest.json')); const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const mainScript = manifest['main.js']; const mainScript = manifest["main.js"];
const mainStyle = manifest['main.css']; const mainStyle = manifest["main.css"];
const scriptUri = vscode.Uri const scriptUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainScript)).with({
.file(path.join(this._extensionPath, 'build', mainScript)) scheme: "vscode-resource",
.with({ scheme: 'vscode-resource' }); });
const styleUri = vscode.Uri const styleUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainStyle)).with({
.file(path.join(this._extensionPath, 'build', mainStyle)) scheme: "vscode-resource",
.with({ scheme: 'vscode-resource' }); });
const customStyleUri = vscode.Uri const customStyleUri = vscode.Uri.file(
.file(path.join(this._workspacePath, this._kanbn.getFolderName(), 'board.css')) path.join(this._workspacePath, this._kanbn.getFolderName(), "board.css")
.with({ scheme: 'vscode-resource' }); ).with({ scheme: "vscode-resource" });
const codiconsUri = vscode.Uri const codiconsUri = vscode.Uri.file(
.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css')) path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
.with({ scheme: 'vscode-resource' }); ).with({ scheme: "vscode-resource" });
// Use a nonce to whitelist which scripts can be run // Use a nonce to whitelist which scripts can be run
const nonce = getNonce(); const nonce = getNonce();
@ -222,7 +215,7 @@ export default class KanbnBoardPanel {
<link rel="stylesheet" type="text/css" href="${customStyleUri}"> <link rel="stylesheet" type="text/css" href="${customStyleUri}">
<link rel="stylesheet" type="text/css" href="${codiconsUri}"> <link rel="stylesheet" type="text/css" href="${codiconsUri}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;"> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
<base href="${vscode.Uri.file(path.join(this._extensionPath, 'build')).with({ scheme: 'vscode-resource' })}/"> <base href="${vscode.Uri.file(path.join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
@ -232,12 +225,3 @@ export default class KanbnBoardPanel {
</html>`; </html>`;
} }
} }
function getNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

View File

@ -1,73 +1,72 @@
import * as path from "path"; import * as path from "path";
import * as vscode from "vscode"; import * as vscode from "vscode";
import getNonce from "./getNonce";
import { v4 as uuidv4 } from "uuid";
export default class KanbnBurndownPanel { export default class KanbnBurndownPanel {
public static currentPanel: KanbnBurndownPanel | undefined;
private static readonly viewType = "react"; private static readonly viewType = "react";
private static panels: Record<string, KanbnBurndownPanel> = {};
private readonly _panel: vscode.WebviewPanel; private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: string; private readonly _extensionPath: string;
private readonly _workspacePath: string; private readonly _workspacePath: string;
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main"); private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
private readonly _panelUuid: string;
private _disposables: vscode.Disposable[] = []; private _disposables: vscode.Disposable[] = [];
public static createOrShow( public static async show(
extensionPath: string, extensionPath: string,
workspacePath: string, workspacePath: string,
kanbn: typeof import("@basementuniverse/kanbn/src/main") kanbn: typeof import("@basementuniverse/kanbn/src/main")
) { ) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
// If we already have a panel, show it, otherwise create a new panel // Create a panel
if (KanbnBurndownPanel.currentPanel) { const panelUuid = uuidv4();
KanbnBurndownPanel.currentPanel._panel.reveal(column); const burndownPanel = new KanbnBurndownPanel(
} else {
KanbnBurndownPanel.currentPanel = new KanbnBurndownPanel(
extensionPath, extensionPath,
workspacePath, workspacePath,
column || vscode.ViewColumn.One, column || vscode.ViewColumn.One,
kanbn kanbn,
panelUuid
); );
} KanbnBurndownPanel.panels[panelUuid] = burndownPanel;
} }
public static async update() { public static async updateAll() {
if (KanbnBurndownPanel.currentPanel) { const panels = Object.values(KanbnBurndownPanel.panels);
if (panels.length === 0) {
return;
}
const kanbn = panels[0]._kanbn;
let index: any; let index: any;
try { try {
index = await KanbnBurndownPanel.currentPanel._kanbn.getIndex(); index = await kanbn.getIndex();
} catch (error) { } catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error); vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return; return;
} }
let tasks: any[]; let tasks: any[];
try { try {
tasks = (await KanbnBurndownPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map( tasks = (await kanbn.loadAllTrackedTasks(index)).map((task) => kanbn.hydrateTask(index, task));
task => KanbnBurndownPanel.currentPanel!._kanbn.hydrateTask(index, task)
);
} catch (error) { } catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error); vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return; return;
} }
KanbnBurndownPanel.currentPanel._panel.webview.postMessage({ panels.forEach((panel) => panel._update(index, tasks));
type: "burndown",
index,
tasks,
dateFormat: KanbnBurndownPanel.currentPanel._kanbn.getDateFormat(index),
});
}
} }
private constructor( private constructor(
extensionPath: string, extensionPath: string,
workspacePath: string, workspacePath: string,
column: vscode.ViewColumn, column: vscode.ViewColumn,
kanbn: typeof import("@basementuniverse/kanbn/src/main") kanbn: typeof import("@basementuniverse/kanbn/src/main"),
panelUuid: string
) { ) {
this._extensionPath = extensionPath; this._extensionPath = extensionPath;
this._workspacePath = workspacePath; this._workspacePath = workspacePath;
this._kanbn = kanbn; this._kanbn = kanbn;
this._panelUuid = panelUuid;
// Create and show a new webview panel // Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, { this._panel = vscode.window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, {
@ -117,9 +116,6 @@ export default class KanbnBurndownPanel {
} }
public dispose() { public dispose() {
KanbnBurndownPanel.currentPanel = undefined;
// Clean up our resources
this._panel.dispose(); this._panel.dispose();
while (this._disposables.length) { while (this._disposables.length) {
const x = this._disposables.pop(); const x = this._disposables.pop();
@ -129,6 +125,15 @@ export default class KanbnBurndownPanel {
} }
} }
private async _update(index: any, tasks: any[]) {
this._panel.webview.postMessage({
type: "burndown",
index,
tasks,
dateFormat: this._kanbn.getDateFormat(index),
});
}
private _getHtmlForWebview() { private _getHtmlForWebview() {
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json")); const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const mainScript = manifest["main.js"]; const mainScript = manifest["main.js"];
@ -170,12 +175,3 @@ export default class KanbnBurndownPanel {
</html>`; </html>`;
} }
} }
function getNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

View File

@ -1,6 +1,7 @@
import * as path from 'path'; import * as path from "path";
import * as vscode from 'vscode'; import * as vscode from "vscode";
import { v4 as uuidv4 } from 'uuid'; import getNonce from "./getNonce";
import { v4 as uuidv4 } from "uuid";
function transformTaskData(taskData: any) { function transformTaskData(taskData: any) {
const result = { const result = {
@ -8,71 +9,69 @@ function transformTaskData(taskData: any) {
name: taskData.name, name: taskData.name,
description: taskData.description, description: taskData.description,
metadata: { metadata: {
created: taskData.metadata.created created: taskData.metadata.created ? new Date(taskData.metadata.created) : new Date(),
? new Date(taskData.metadata.created)
: new Date(),
updated: new Date(), updated: new Date(),
assigned: taskData.metadata.assigned, assigned: taskData.metadata.assigned,
progress: taskData.progress, progress: taskData.progress,
tags: taskData.metadata.tags tags: taskData.metadata.tags,
} as any, } as any,
relations: taskData.relations, relations: taskData.relations,
subTasks: taskData.subTasks, subTasks: taskData.subTasks,
comments: taskData.comments.map((comment: any) => ({ comments: taskData.comments.map((comment: any) => ({
author: comment.author, author: comment.author,
date: new Date(Date.parse(comment.date)), date: new Date(Date.parse(comment.date)),
text: comment.text text: comment.text,
})) })),
} as any; } as any;
// Add assigned // Add assigned
if (taskData.metadata.assigned) { if (taskData.metadata.assigned) {
result.metadata['assigned'] = taskData.metadata.assigned; result.metadata["assigned"] = taskData.metadata.assigned;
} }
// Add progress // Add progress
if (taskData.progress > 0) { if (taskData.progress > 0) {
result.metadata['progress'] = taskData.progress; result.metadata["progress"] = taskData.progress;
} }
// Add tags // Add tags
if (taskData.metadata.tags.length) { if (taskData.metadata.tags.length) {
result.metadata['tags'] = taskData.metadata.tags; result.metadata["tags"] = taskData.metadata.tags;
} }
// Add due, started and completed dates if present // Add due, started and completed dates if present
if (taskData.metadata.due) { if (taskData.metadata.due) {
result.metadata['due'] = new Date(Date.parse(taskData.metadata.due)); result.metadata["due"] = new Date(Date.parse(taskData.metadata.due));
} }
if (taskData.metadata.started) { if (taskData.metadata.started) {
result.metadata['started'] = new Date(Date.parse(taskData.metadata.started)); result.metadata["started"] = new Date(Date.parse(taskData.metadata.started));
} }
if (taskData.metadata.completed) { if (taskData.metadata.completed) {
result.metadata['completed'] = new Date(Date.parse(taskData.metadata.completed)); result.metadata["completed"] = new Date(Date.parse(taskData.metadata.completed));
} }
return result; return result;
} }
export default class KanbnTaskPanel { export default class KanbnTaskPanel {
private static readonly viewType = 'react'; private static readonly viewType = "react";
private static panels: Record<string, KanbnTaskPanel> = {}; private static panels: Record<string, KanbnTaskPanel> = {};
private readonly _panel: vscode.WebviewPanel; private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: string; private readonly _extensionPath: string;
private readonly _workspacePath: string; private readonly _workspacePath: string;
private readonly _kanbn: typeof import('@basementuniverse/kanbn/src/main'); private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
private _taskId: string|null;
private _columnName: string|null;
private readonly _panelUuid: string; private readonly _panelUuid: string;
private _taskId: string | null;
private _columnName: string | null;
private _disposables: vscode.Disposable[] = []; private _disposables: vscode.Disposable[] = [];
public static async show( public static async show(
extensionPath: string, extensionPath: string,
workspacePath: string, workspacePath: string,
kanbn: typeof import('@basementuniverse/kanbn/src/main'), kanbn: typeof import("@basementuniverse/kanbn/src/main"),
taskId: string|null, taskId: string | null,
columnName: string|null columnName: string | null
) { ) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
@ -95,9 +94,9 @@ export default class KanbnTaskPanel {
extensionPath: string, extensionPath: string,
workspacePath: string, workspacePath: string,
column: vscode.ViewColumn, column: vscode.ViewColumn,
kanbn: typeof import('@basementuniverse/kanbn/src/main'), kanbn: typeof import("@basementuniverse/kanbn/src/main"),
taskId: string|null, taskId: string | null,
columnName: string|null, columnName: string | null,
panelUuid: string panelUuid: string
) { ) {
this._extensionPath = extensionPath; this._extensionPath = extensionPath;
@ -108,7 +107,7 @@ export default class KanbnTaskPanel {
this._panelUuid = panelUuid; this._panelUuid = panelUuid;
// Create and show a new webview panel // Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(KanbnTaskPanel.viewType, 'New task', column, { this._panel = vscode.window.createWebviewPanel(KanbnTaskPanel.viewType, "New task", column, {
// Enable javascript in the webview // Enable javascript in the webview
enableScripts: true, enableScripts: true,
@ -117,19 +116,19 @@ export default class KanbnTaskPanel {
// Restrict the webview to only loading content from allowed paths // Restrict the webview to only loading content from allowed paths
localResourceRoots: [ localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, 'build')), vscode.Uri.file(path.join(this._extensionPath, "build")),
vscode.Uri.file(path.join(this._workspacePath, this._kanbn.getFolderName())), vscode.Uri.file(path.join(this._workspacePath, this._kanbn.getFolderName())),
vscode.Uri.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist')) vscode.Uri.file(path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
] ],
}); });
(this._panel as any).iconPath = { (this._panel as any).iconPath = {
light: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'task_light.svg')), light: vscode.Uri.file(path.join(this._extensionPath, "resources", "task_light.svg")),
dark: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'task_dark.svg')) dark: vscode.Uri.file(path.join(this._extensionPath, "resources", "task_dark.svg")),
}; };
// Set the webview's title to the kanbn task name // Set the webview's title to the kanbn task name
if (this._taskId !== null) { if (this._taskId !== null) {
this._kanbn.getTask(this._taskId).then(task => { this._kanbn.getTask(this._taskId).then((task) => {
this._panel.title = task.name; this._panel.title = task.name;
}); });
} }
@ -142,58 +141,61 @@ export default class KanbnTaskPanel {
this._panel.onDidDispose(() => this.dispose(), null, this._disposables); this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
// Handle messages from the webview // Handle messages from the webview
this._panel.webview.onDidReceiveMessage(async message => { this._panel.webview.onDidReceiveMessage(
async (message) => {
switch (message.command) { switch (message.command) {
// Display error message // Display error message
case 'error': case "error":
vscode.window.showErrorMessage(message.text); vscode.window.showErrorMessage(message.text);
return; return;
// Update the task webview panel title // Update the task webview panel title
case 'kanbn.updatePanelTitle': case "kanbn.updatePanelTitle":
this._panel.title = message.title; this._panel.title = message.title;
return; return;
// Create a task // Create a task
case 'kanbn.create': case "kanbn.create":
await this._kanbn.createTask(transformTaskData(message.taskData), message.taskData.column); await this._kanbn.createTask(transformTaskData(message.taskData), message.taskData.column);
KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id; KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id;
KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column; KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column;
KanbnTaskPanel.panels[message.panelUuid].update(); KanbnTaskPanel.panels[message.panelUuid].update();
if (vscode.workspace.getConfiguration('kanbn').get('showTaskNotifications')) { if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Created task '${message.taskData.name}'.`); vscode.window.showInformationMessage(`Created task '${message.taskData.name}'.`);
} }
return; return;
// Update a task // Update a task
case 'kanbn.update': case "kanbn.update":
await this._kanbn.updateTask(message.taskId, transformTaskData(message.taskData), message.taskData.column); await this._kanbn.updateTask(message.taskId, transformTaskData(message.taskData), message.taskData.column);
KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id; KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id;
KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column; KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column;
KanbnTaskPanel.panels[message.panelUuid].update(); KanbnTaskPanel.panels[message.panelUuid].update();
if (vscode.workspace.getConfiguration('kanbn').get('showTaskNotifications')) { if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Updated task '${message.taskData.name}'.`); vscode.window.showInformationMessage(`Updated task '${message.taskData.name}'.`);
} }
return; return;
// Delete a task and close the webview panel // Delete a task and close the webview panel
case 'kanbn.delete': case "kanbn.delete":
vscode.window.showInformationMessage(`Delete task '${message.taskData.name}'?`, 'Yes', 'No').then( vscode.window
async value => { .showInformationMessage(`Delete task '${message.taskData.name}'?`, "Yes", "No")
if (value === 'Yes') { .then(async (value) => {
if (value === "Yes") {
await this._kanbn.deleteTask(message.taskId, true); await this._kanbn.deleteTask(message.taskId, true);
KanbnTaskPanel.panels[message.panelUuid].dispose(); KanbnTaskPanel.panels[message.panelUuid].dispose();
delete KanbnTaskPanel.panels[message.panelUuid]; delete KanbnTaskPanel.panels[message.panelUuid];
if (vscode.workspace.getConfiguration('kanbn').get('showTaskNotifications')) { if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Deleted task '${message.taskData.name}'.`); vscode.window.showInformationMessage(`Deleted task '${message.taskData.name}'.`);
} }
} }
} });
);
return; return;
} }
}, null, this._disposables); },
null,
this._disposables
);
} }
public dispose() { public dispose() {
@ -216,19 +218,17 @@ export default class KanbnTaskPanel {
} }
let tasks: any[]; let tasks: any[];
try { try {
tasks = (await this._kanbn.loadAllTrackedTasks(index)).map( tasks = (await this._kanbn.loadAllTrackedTasks(index)).map((task) => ({
task => ({
uuid: uuidv4(), uuid: uuidv4(),
...this._kanbn.hydrateTask(index, task) ...this._kanbn.hydrateTask(index, task),
}) }));
);
} catch (error) { } catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error); vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return; return;
} }
let task = null; let task = null;
if (this._taskId) { if (this._taskId) {
task = tasks.find(t => t.id === this._taskId) ?? null; task = tasks.find((t) => t.id === this._taskId) ?? null;
} }
// If no columnName is specified, use the first column // If no columnName is specified, use the first column
@ -238,32 +238,32 @@ export default class KanbnTaskPanel {
// Send task data to the webview // Send task data to the webview
this._panel.webview.postMessage({ this._panel.webview.postMessage({
type: 'task', type: "task",
index, index,
task, task,
tasks, tasks,
columnName: this._columnName, columnName: this._columnName,
dateFormat: this._kanbn.getDateFormat(index), dateFormat: this._kanbn.getDateFormat(index),
panelUuid: this._panelUuid panelUuid: this._panelUuid,
}); });
} }
private _getHtmlForWebview() { private _getHtmlForWebview() {
const manifest = require(path.join(this._extensionPath, 'build', 'asset-manifest.json')); const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const mainScript = manifest['main.js']; const mainScript = manifest["main.js"];
const mainStyle = manifest['main.css']; const mainStyle = manifest["main.css"];
const scriptUri = vscode.Uri const scriptUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainScript)).with({
.file(path.join(this._extensionPath, 'build', mainScript)) scheme: "vscode-resource",
.with({ scheme: 'vscode-resource' }); });
const styleUri = vscode.Uri const styleUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainStyle)).with({
.file(path.join(this._extensionPath, 'build', mainStyle)) scheme: "vscode-resource",
.with({ scheme: 'vscode-resource' }); });
const customStyleUri = vscode.Uri const customStyleUri = vscode.Uri.file(
.file(path.join(this._workspacePath, this._kanbn.getFolderName(), 'board.css')) path.join(this._workspacePath, this._kanbn.getFolderName(), "board.css")
.with({ scheme: 'vscode-resource' }); ).with({ scheme: "vscode-resource" });
const codiconsUri = vscode.Uri const codiconsUri = vscode.Uri.file(
.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css')) path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
.with({ scheme: 'vscode-resource' }); ).with({ scheme: "vscode-resource" });
// Use a nonce to whitelist which scripts can be run // Use a nonce to whitelist which scripts can be run
const nonce = getNonce(); const nonce = getNonce();
@ -279,7 +279,7 @@ export default class KanbnTaskPanel {
<link rel="stylesheet" type="text/css" href="${customStyleUri}"> <link rel="stylesheet" type="text/css" href="${customStyleUri}">
<link rel="stylesheet" type="text/css" href="${codiconsUri}"> <link rel="stylesheet" type="text/css" href="${codiconsUri}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;"> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
<base href="${vscode.Uri.file(path.join(this._extensionPath, 'build')).with({ scheme: 'vscode-resource' })}/"> <base href="${vscode.Uri.file(path.join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
@ -289,12 +289,3 @@ export default class KanbnTaskPanel {
</html>`; </html>`;
} }
} }
function getNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

View File

@ -1,29 +1,28 @@
import * as vscode from 'vscode'; import * as vscode from "vscode";
import KanbnStatusBarItem from './KanbnStatusBarItem'; import KanbnStatusBarItem from "./KanbnStatusBarItem";
import KanbnBoardPanel from './KanbnBoardPanel'; import KanbnBoardPanel from "./KanbnBoardPanel";
import KanbnBurndownPanel from './KanbnBurndownPanel'; import KanbnBurndownPanel from "./KanbnBurndownPanel";
import KanbnTaskPanel from './KanbnTaskPanel'; import KanbnTaskPanel from "./KanbnTaskPanel";
let kanbnStatusBarItem: KanbnStatusBarItem; let kanbnStatusBarItem: KanbnStatusBarItem;
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
// Register a command to initialise Kanbn in the current workspace. This command will be invoked when the status // Register a command to initialise Kanbn in the current workspace. This command will be invoked when the status
// bar item is clicked in a workspace where Kanbn isn't already initialised. // bar item is clicked in a workspace where Kanbn isn't already initialised.
context.subscriptions.push(vscode.commands.registerCommand('kanbn.init', async () => { context.subscriptions.push(
vscode.commands.registerCommand("kanbn.init", async () => {
// If no workspace folder is opened, we can't initialise kanbn // If no workspace folder is opened, we can't initialise kanbn
if (vscode.workspace.workspaceFolders === undefined) { if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage('You need to open a workspace before initialising Kanbn.'); vscode.window.showErrorMessage("You need to open a workspace before initialising Kanbn.");
return; return;
} }
// Set the node process directory and import kanbn // Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import('@basementuniverse/kanbn/src/main'); const kanbn = await import("@basementuniverse/kanbn/src/main");
// If kanbn is already initialised, get the project name // If kanbn is already initialised, get the project name
let projectName = ''; let projectName = "";
if (await kanbn.initialised()) { if (await kanbn.initialised()) {
projectName = (await kanbn.getIndex()).name; projectName = (await kanbn.getIndex()).name;
} }
@ -31,111 +30,100 @@ export async function activate(context: vscode.ExtensionContext) {
// Prompt for a new project name // Prompt for a new project name
const newProjectName = await vscode.window.showInputBox({ const newProjectName = await vscode.window.showInputBox({
value: projectName, value: projectName,
placeHolder: 'The project name.', placeHolder: "The project name.",
validateInput: text => { validateInput: (text) => {
return text.length < 1 ? 'The project name cannot be empty.' : null; return text.length < 1 ? "The project name cannot be empty." : null;
} },
}); });
// If the input prompt wasn't cancelled, initialise kanbn // If the input prompt wasn't cancelled, initialise kanbn
if (newProjectName !== undefined) { if (newProjectName !== undefined) {
await kanbn.initialise({ await kanbn.initialise({
name: newProjectName name: newProjectName,
}); });
vscode.window.showInformationMessage(`Initialised Kanbn project '${newProjectName}'.`); vscode.window.showInformationMessage(`Initialised Kanbn project '${newProjectName}'.`);
KanbnBoardPanel.update(); KanbnBoardPanel.update();
} }
kanbnStatusBarItem.update(); kanbnStatusBarItem.update();
})); })
);
// Register a command to open the kanbn board. This command will be invoked when the status bar item is clicked // Register a command to open the kanbn board. This command will be invoked when the status bar item is clicked
// in a workspace where kanbn has already been initialised. // in a workspace where kanbn has already been initialised.
context.subscriptions.push(vscode.commands.registerCommand('kanbn.board', async () => { context.subscriptions.push(
vscode.commands.registerCommand("kanbn.board", async () => {
// If no workspace folder is opened, we can't open the kanbn board // If no workspace folder is opened, we can't open the kanbn board
if (vscode.workspace.workspaceFolders === undefined) { if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage('You need to open a workspace before viewing the Kanbn board.'); vscode.window.showErrorMessage("You need to open a workspace before viewing the Kanbn board.");
return; return;
} }
// Set the node process directory and import kanbn // Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import('@basementuniverse/kanbn/src/main'); const kanbn = await import("@basementuniverse/kanbn/src/main");
// If kanbn is initialised, view the kanbn board // If kanbn is initialised, view the kanbn board
if (await kanbn.initialised()) { if (await kanbn.initialised()) {
KanbnBoardPanel.createOrShow( KanbnBoardPanel.createOrShow(context.extensionPath, vscode.workspace.workspaceFolders[0].uri.fsPath, kanbn);
context.extensionPath,
vscode.workspace.workspaceFolders[0].uri.fsPath,
kanbn
);
KanbnBoardPanel.update(); KanbnBoardPanel.update();
} else { } else {
vscode.window.showErrorMessage('You need to initialise Kanbn before viewing the Kanbn board.'); vscode.window.showErrorMessage("You need to initialise Kanbn before viewing the Kanbn board.");
} }
kanbnStatusBarItem.update(); kanbnStatusBarItem.update();
})); })
);
// Register a command to add a new kanbn task. // Register a command to add a new kanbn task.
context.subscriptions.push(vscode.commands.registerCommand('kanbn.addTask', async () => { context.subscriptions.push(
vscode.commands.registerCommand("kanbn.addTask", async () => {
// If no workspace folder is opened, we can't add a new task // If no workspace folder is opened, we can't add a new task
if (vscode.workspace.workspaceFolders === undefined) { if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage('You need to open a workspace before adding a new task.'); vscode.window.showErrorMessage("You need to open a workspace before adding a new task.");
return; return;
} }
// Set the node process directory and import kanbn // Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import('@basementuniverse/kanbn/src/main'); const kanbn = await import("@basementuniverse/kanbn/src/main");
// If kanbn is initialised, open the task webview // If kanbn is initialised, open the task webview
if (await kanbn.initialised()) { if (await kanbn.initialised()) {
KanbnTaskPanel.show( KanbnTaskPanel.show(context.extensionPath, vscode.workspace.workspaceFolders[0].uri.fsPath, kanbn, null, null);
context.extensionPath,
vscode.workspace.workspaceFolders[0].uri.fsPath,
kanbn,
null,
null
);
} else { } else {
vscode.window.showErrorMessage('You need to initialise Kanbn before adding a new task.'); vscode.window.showErrorMessage("You need to initialise Kanbn before adding a new task.");
} }
})); })
);
// Register a command to open a burndown chart. // Register a command to open a burndown chart.
context.subscriptions.push(vscode.commands.registerCommand('kanbn.burndown', async () => { context.subscriptions.push(
vscode.commands.registerCommand("kanbn.burndown", async () => {
// If no workspace folder is opened, we can't open the burndown chart // If no workspace folder is opened, we can't open the burndown chart
if (vscode.workspace.workspaceFolders === undefined) { if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage('You need to open a workspace before viewing the burndown chart.'); vscode.window.showErrorMessage("You need to open a workspace before viewing the burndown chart.");
return; return;
} }
// Set the node process directory and import kanbn // Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import('@basementuniverse/kanbn/src/main'); const kanbn = await import("@basementuniverse/kanbn/src/main");
// If kanbn is initialised, view the burndown chart // If kanbn is initialised, view the burndown chart
if (await kanbn.initialised()) { if (await kanbn.initialised()) {
KanbnBurndownPanel.createOrShow( KanbnBurndownPanel.show(context.extensionPath, vscode.workspace.workspaceFolders[0].uri.fsPath, kanbn);
context.extensionPath, KanbnBurndownPanel.updateAll();
vscode.workspace.workspaceFolders[0].uri.fsPath,
kanbn
);
KanbnBurndownPanel.update();
} else { } else {
vscode.window.showErrorMessage('You need to initialise Kanbn before viewing the burndown chart.'); vscode.window.showErrorMessage("You need to initialise Kanbn before viewing the burndown chart.");
} }
kanbnStatusBarItem.update(); kanbnStatusBarItem.update();
})); })
);
// If a workspace folder is open, add a status bar item and start watching for file changes // If a workspace folder is open, add a status bar item and start watching for file changes
if (vscode.workspace.workspaceFolders !== undefined) { if (vscode.workspace.workspaceFolders !== undefined) {
// Set the node process directory and import kanbn // Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import('@basementuniverse/kanbn/src/main'); const kanbn = await import("@basementuniverse/kanbn/src/main");
// Create status bar item // Create status bar item
kanbnStatusBarItem = new KanbnStatusBarItem(context, kanbn); kanbnStatusBarItem = new KanbnStatusBarItem(context, kanbn);
@ -144,18 +132,18 @@ export async function activate(context: vscode.ExtensionContext) {
// Initialise file watcher // Initialise file watcher
const uri = vscode.workspace.workspaceFolders[0].uri.fsPath; const uri = vscode.workspace.workspaceFolders[0].uri.fsPath;
const fileWatcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern( const fileWatcher = vscode.workspace.createFileSystemWatcher(
uri, new vscode.RelativePattern(uri, `${kanbn.getFolderName()}/**.*`)
`${kanbn.getFolderName()}/**.*` );
));
fileWatcher.onDidChange(() => { fileWatcher.onDidChange(() => {
kanbnStatusBarItem.update(); kanbnStatusBarItem.update();
KanbnBoardPanel.update(); KanbnBoardPanel.update();
KanbnBurndownPanel.updateAll();
}); });
} }
// Handle configuration changes // Handle configuration changes
vscode.workspace.onDidChangeConfiguration(e => { vscode.workspace.onDidChangeConfiguration((e) => {
kanbnStatusBarItem.update(); kanbnStatusBarItem.update();
KanbnBoardPanel.update(); KanbnBoardPanel.update();
}); });

8
ext-src/getNonce.ts Normal file
View File

@ -0,0 +1,8 @@
export default function getNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}

View File

@ -30,7 +30,9 @@ function App() {
const [burndownData, setBurndownData] = useState({ series: [] }); const [burndownData, setBurndownData] = useState({ series: [] });
window.addEventListener('message', event => { window.addEventListener('message', event => {
const tasks = Object.fromEntries(event.data.tasks.map(task => [task.id, task])); const tasks = event.data.tasks
? Object.fromEntries(event.data.tasks.map(task => [task.id, task]))
: {};
switch (event.data.type) { switch (event.data.type) {
case 'index': case 'index':
setName(event.data.index.name); setName(event.data.index.name);
@ -66,7 +68,11 @@ function App() {
case 'burndown': case 'burndown':
setName(event.data.index.name); setName(event.data.index.name);
setTasks(tasks); setTasks(tasks);
setSprints(event.data.index.options.sprints || []); setSprints(
'sprints' in event.data.index.options
? event.data.index.options.sprints
: []
);
setBurndownData(event.data.burndownData); setBurndownData(event.data.burndownData);
break; break;
} }
@ -111,6 +117,7 @@ function App() {
tasks={tasks} tasks={tasks}
sprints={sprints} sprints={sprints}
burndownData={burndownData} burndownData={burndownData}
vscode={vscode}
/> />
} }
</React.Fragment> </React.Fragment>

View File

@ -1,7 +1,9 @@
import React from 'react'; import React, { useState } from 'react';
import { LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts'; import { ResponsiveContainer, LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts';
import VSCodeApi from "./VSCodeApi";
import formatDate from 'dateformat';
const Burndown = ({ name, tasks, sprints, burndownData }: { const Burndown = ({ name, tasks, sprints, burndownData, vscode }: {
name: string, name: string,
tasks: Record<string, KanbnTask>, tasks: Record<string, KanbnTask>,
sprints: KanbnSprint[], sprints: KanbnSprint[],
@ -15,8 +17,14 @@ const Burndown = ({ name, tasks, sprints, burndownData }: {
y: number y: number
}> }>
}> }>
} },
vscode: VSCodeApi
}) => { }) => {
const [sprintMode, setSprintMode] = useState(sprints.length > 0);
const [sprint, setSprint] = useState(null);
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const data = [ const data = [
{name: 'one', uv: 20}, {name: 'one', uv: 20},
{name: 'two', uv: 80}, {name: 'two', uv: 80},
@ -26,14 +34,82 @@ const Burndown = ({ name, tasks, sprints, burndownData }: {
return ( return (
<React.Fragment> <React.Fragment>
<div>burndown chart</div> <div className="kanbn-header">
<LineChart width={600} height={300} data={data} margin={{ top: 5, right: 20, bottom: 5, left: 0 }}> <h1 className="kanbn-header-name">
<p>{name}</p>
<div className="kanbn-burndown-settings">
<form>
{
sprintMode
? <select className="kanbn-burndown-settings-sprint-select" onChange={e => { console.log(e); }}>
{
sprints.length > 0
? sprints.map((sprint, i) => {
return (
<option value={i}>{sprint.name}</option>
);
})
: <option disabled>No sprints</option>
}
</select>
: <React.Fragment>
<input
type="date"
className="kanbn-burndown-settings-input kanbn-burndown-settings-start-date"
onChange={e => { console.log(e); }}
/>
<input
type="date"
className="kanbn-burndown-settings-input kanbn-burndown-settings-end-date"
onChange={e => { console.log(e); }}
/>
</React.Fragment>
}
<button
type="button"
className={[
'kanbn-header-button',
'kanbn-burndown-settings-sprint-mode',
sprintMode ? 'kanbn-header-button-active' : 'kanbn-header-button-inactive'
].join(' ')}
onClick={() => {
setSprintMode(true);
// update burndown chart
}}
title="View sprint burndown"
>
<i className="codicon codicon-rocket"></i>
</button>
<button
type="button"
className={[
'kanbn-header-button',
'kanbn-burndown-settings-date-mode',
sprintMode ? 'kanbn-header-button-inactive' : 'kanbn-header-button-active'
].join(' ')}
onClick={() => {
setSprintMode(false);
// update burndown chart
}}
title="View date-range burndown"
>
<i className="codicon codicon-clock"></i>
</button>
</form>
</div>
</h1>
</div>
<div className="kanbn-burndown">
<ResponsiveContainer width="100%" height="100%" className="kanbn-burndown-chart">
<LineChart data={data}>
<Line type="monotone" dataKey="uv" stroke="#8884d8" /> <Line type="monotone" dataKey="uv" stroke="#8884d8" />
<CartesianGrid stroke="#ccc" strokeDasharray="5 5" /> <CartesianGrid stroke="#ccc" strokeDasharray="5 5" />
<XAxis dataKey="name" /> <XAxis dataKey="name" />
<YAxis /> <YAxis />
<Tooltip /> <Tooltip />
</LineChart> </LineChart>
</ResponsiveContainer>
</div>
</React.Fragment> </React.Fragment>
); );
}; };

View File

@ -20,7 +20,7 @@ body.vscode-high-contrast {
.kanbn-header-name p { .kanbn-header-name p {
display: inline-block; display: inline-block;
flex: 1; flex: 2;
margin: 0; margin: 0;
padding: 4px 0; padding: 4px 0;
} }
@ -42,6 +42,8 @@ body.vscode-high-contrast {
padding: 8px; padding: 8px;
background-color: var(--vscode-input-background); background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground); color: var(--vscode-input-foreground);
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
border: 1px transparent solid; border: 1px transparent solid;
} }
@ -340,12 +342,13 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
margin: 8px 0; margin: 8px 0;
background-color: var(--vscode-input-background); background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground); color: var(--vscode-input-foreground);
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
border: 1px transparent solid; border: 1px transparent solid;
} }
.kanbn-task-editor-field-input[type=date] { .kanbn-task-editor-field-input[type=date] {
font-family: var(--vscode-font-family); padding: 6px 8px;
font-size: var(--vscode-font-size);
} }
.kanbn-task-editor-field-select { .kanbn-task-editor-field-select {
@ -477,3 +480,60 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
.kanbn-task-editor-field-comment-text .kanbn-task-editor-field-textarea { .kanbn-task-editor-field-comment-text .kanbn-task-editor-field-textarea {
min-height: 90px; min-height: 90px;
} }
.kanbn-burndown-settings {
position: relative;
flex: 1;
}
.kanbn-burndown-settings form {
width: 100%;
display: flex;
white-space: nowrap;
}
.kanbn-burndown-settings-sprint-select {
box-sizing: border-box;
flex: 1;
padding: 8px;
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px transparent solid;
}
.kanbn-burndown-settings-input {
box-sizing: border-box;
display: block;
flex: 0.5;
padding: 8px;
background-color: var(--vscode-input-background);
color: var(--vscode-input-foreground);
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
border: 1px transparent solid;
}
.kanbn-burndown-settings-input[type=date] {
padding: 6px 8px;
margin-left: 8px;
}
.kanbn-burndown-settings-sprint-select:hover,
.kanbn-burndown-settings-sprint-select:focus,
.kanbn-burndown-settings-input:hover,
.kanbn-burndown-settings-input:focus {
border-color: var(--vscode-input-border);
}
body.vscode-dark .kanbn-burndown-settings-input[type="date"]::-webkit-calendar-picker-indicator {
filter: invert(1);
}
.kanbn-header-button-inactive {
opacity: 0.6;
}
.kanbn-burndown {
height: 85vh;
width: 95vw;
}