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 vscode from 'vscode';
import KanbnTaskPanel from './KanbnTaskPanel';
import KanbnBurndownPanel from './KanbnBurndownPanel';
import * as path from "path";
import * as vscode from "vscode";
import getNonce from "./getNonce";
import KanbnTaskPanel from "./KanbnTaskPanel";
import KanbnBurndownPanel from "./KanbnBurndownPanel";
export default class KanbnBoardPanel {
public static currentPanel: KanbnBoardPanel | undefined;
private static readonly viewType = 'react';
private static readonly viewType = "react";
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: 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[] = [];
public static createOrShow(
extensionPath: 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;
@ -45,23 +46,23 @@ export default class KanbnBoardPanel {
}
let tasks: any[];
try {
tasks = (await KanbnBoardPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map(
task => KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task)
tasks = (await KanbnBoardPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map((task) =>
KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task)
);
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
KanbnBoardPanel.currentPanel._panel.webview.postMessage({
type: 'index',
type: "index",
index,
tasks,
hiddenColumns: index.options.hiddenColumns ?? [],
startedColumns: index.options.startedColumns ?? [],
completedColumns: index.options.completedColumns ?? [],
dateFormat: KanbnBoardPanel.currentPanel._kanbn.getDateFormat(index),
showBurndownButton: vscode.workspace.getConfiguration('kanbn').get('showBurndownButton'),
showSprintButton: vscode.workspace.getConfiguration('kanbn').get('showSprintButton')
showBurndownButton: vscode.workspace.getConfiguration("kanbn").get("showBurndownButton"),
showSprintButton: vscode.workspace.getConfiguration("kanbn").get("showSprintButton"),
});
}
}
@ -70,14 +71,14 @@ export default class KanbnBoardPanel {
extensionPath: string,
workspacePath: string,
column: vscode.ViewColumn,
kanbn: typeof import('@basementuniverse/kanbn/src/main')
kanbn: typeof import("@basementuniverse/kanbn/src/main")
) {
this._extensionPath = extensionPath;
this._workspacePath = workspacePath;
this._kanbn = kanbn;
// 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
enableScripts: true,
@ -86,18 +87,18 @@ export default class KanbnBoardPanel {
// Restrict the webview to only loading content from allowed paths
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._extensionPath, 'node_modules', 'vscode-codicons', 'dist'))
]
vscode.Uri.file(path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
],
});
(this._panel as any).iconPath = {
light: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'project_light.svg')),
dark: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'project_dark.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")),
};
// 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;
});
@ -109,73 +110,65 @@ export default class KanbnBoardPanel {
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
// Handle messages from the webview
this._panel.webview.onDidReceiveMessage(async message => {
switch (message.command) {
this._panel.webview.onDidReceiveMessage(
async (message) => {
switch (message.command) {
// Display error message
case "error":
vscode.window.showErrorMessage(message.text);
return;
// Display error message
case 'error':
vscode.window.showErrorMessage(message.text);
return;
case "kanbn.task":
KanbnTaskPanel.show(
this._extensionPath,
this._workspacePath,
this._kanbn,
message.taskId,
message.columnName
);
return;
case 'kanbn.task':
KanbnTaskPanel.show(
this._extensionPath,
this._workspacePath,
this._kanbn,
message.taskId,
message.columnName
);
return;
// Move a task
case 'kanbn.move':
try {
await kanbn.moveTask(message.task, message.columnName, message.position);
} catch (e) {
vscode.window.showErrorMessage(e.message);
}
return;
// Create a task
case 'kanbn.addTask':
KanbnTaskPanel.show(
this._extensionPath,
this._workspacePath,
this._kanbn,
null,
message.columnName
);
return;
// Open a burndown chart
case 'kanbn.burndown':
KanbnBurndownPanel.createOrShow(
this._extensionPath,
this._workspacePath,
this._kanbn
);
KanbnBurndownPanel.update();
return;
// Start a new sprint
case 'kanbn.sprint':
// Prompt for a sprint name
const newSprintName = await vscode.window.showInputBox({
placeHolder: 'The sprint name.'
});
// If the input prompt wasn't cancelled, start a new sprint
if (newSprintName !== undefined) {
// Move a task
case "kanbn.move":
try {
await kanbn.sprint(newSprintName, '', new Date());
await kanbn.moveTask(message.task, message.columnName, message.position);
} catch (e) {
vscode.window.showErrorMessage(e.message);
}
}
return;
}
}, null, this._disposables);
return;
// Create a task
case "kanbn.addTask":
KanbnTaskPanel.show(this._extensionPath, this._workspacePath, this._kanbn, null, message.columnName);
return;
// Open a burndown chart
case "kanbn.burndown":
KanbnBurndownPanel.show(this._extensionPath, this._workspacePath, this._kanbn);
KanbnBurndownPanel.updateAll();
return;
// Start a new sprint
case "kanbn.sprint":
// Prompt for a sprint name
const newSprintName = await vscode.window.showInputBox({
placeHolder: "The sprint name.",
});
// If the input prompt wasn't cancelled, start a new sprint
if (newSprintName !== undefined) {
try {
await kanbn.sprint(newSprintName, "", new Date());
} catch (e) {
vscode.window.showErrorMessage(e.message);
}
}
return;
}
},
null,
this._disposables
);
}
public dispose() {
@ -192,21 +185,21 @@ export default class KanbnBoardPanel {
}
private _getHtmlForWebview() {
const manifest = require(path.join(this._extensionPath, 'build', 'asset-manifest.json'));
const mainScript = manifest['main.js'];
const mainStyle = manifest['main.css'];
const scriptUri = vscode.Uri
.file(path.join(this._extensionPath, 'build', mainScript))
.with({ scheme: 'vscode-resource' });
const styleUri = vscode.Uri
.file(path.join(this._extensionPath, 'build', mainStyle))
.with({ scheme: 'vscode-resource' });
const customStyleUri = vscode.Uri
.file(path.join(this._workspacePath, this._kanbn.getFolderName(), 'board.css'))
.with({ scheme: 'vscode-resource' });
const codiconsUri = vscode.Uri
.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css'))
.with({ scheme: 'vscode-resource' });
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const mainScript = manifest["main.js"];
const mainStyle = manifest["main.css"];
const scriptUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainScript)).with({
scheme: "vscode-resource",
});
const styleUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainStyle)).with({
scheme: "vscode-resource",
});
const customStyleUri = vscode.Uri.file(
path.join(this._workspacePath, this._kanbn.getFolderName(), "board.css")
).with({ scheme: "vscode-resource" });
const codiconsUri = vscode.Uri.file(
path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
).with({ scheme: "vscode-resource" });
// Use a nonce to whitelist which scripts can be run
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="${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:;">
<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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@ -232,12 +225,3 @@ export default class KanbnBoardPanel {
</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 vscode from "vscode";
import getNonce from "./getNonce";
import { v4 as uuidv4 } from "uuid";
export default class KanbnBurndownPanel {
public static currentPanel: KanbnBurndownPanel | undefined;
private static readonly viewType = "react";
private static panels: Record<string, KanbnBurndownPanel> = {};
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: string;
private readonly _workspacePath: string;
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
private readonly _panelUuid: string;
private _disposables: vscode.Disposable[] = [];
public static createOrShow(
public static async show(
extensionPath: string,
workspacePath: string,
kanbn: typeof import("@basementuniverse/kanbn/src/main")
) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
// If we already have a panel, show it, otherwise create a new panel
if (KanbnBurndownPanel.currentPanel) {
KanbnBurndownPanel.currentPanel._panel.reveal(column);
} else {
KanbnBurndownPanel.currentPanel = new KanbnBurndownPanel(
extensionPath,
workspacePath,
column || vscode.ViewColumn.One,
kanbn
);
}
// Create a panel
const panelUuid = uuidv4();
const burndownPanel = new KanbnBurndownPanel(
extensionPath,
workspacePath,
column || vscode.ViewColumn.One,
kanbn,
panelUuid
);
KanbnBurndownPanel.panels[panelUuid] = burndownPanel;
}
public static async update() {
if (KanbnBurndownPanel.currentPanel) {
let index: any;
try {
index = await KanbnBurndownPanel.currentPanel._kanbn.getIndex();
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
let tasks: any[];
try {
tasks = (await KanbnBurndownPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map(
task => KanbnBurndownPanel.currentPanel!._kanbn.hydrateTask(index, task)
);
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
KanbnBurndownPanel.currentPanel._panel.webview.postMessage({
type: "burndown",
index,
tasks,
dateFormat: KanbnBurndownPanel.currentPanel._kanbn.getDateFormat(index),
});
public static async updateAll() {
const panels = Object.values(KanbnBurndownPanel.panels);
if (panels.length === 0) {
return;
}
const kanbn = panels[0]._kanbn;
let index: any;
try {
index = await kanbn.getIndex();
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
let tasks: any[];
try {
tasks = (await kanbn.loadAllTrackedTasks(index)).map((task) => kanbn.hydrateTask(index, task));
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
panels.forEach((panel) => panel._update(index, tasks));
}
private constructor(
extensionPath: string,
workspacePath: string,
column: vscode.ViewColumn,
kanbn: typeof import("@basementuniverse/kanbn/src/main")
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
panelUuid: string
) {
this._extensionPath = extensionPath;
this._workspacePath = workspacePath;
this._kanbn = kanbn;
this._panelUuid = panelUuid;
// Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, {
@ -117,9 +116,6 @@ export default class KanbnBurndownPanel {
}
public dispose() {
KanbnBurndownPanel.currentPanel = undefined;
// Clean up our resources
this._panel.dispose();
while (this._disposables.length) {
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() {
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const mainScript = manifest["main.js"];
@ -170,12 +175,3 @@ export default class KanbnBurndownPanel {
</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 vscode from 'vscode';
import { v4 as uuidv4 } from 'uuid';
import * as path from "path";
import * as vscode from "vscode";
import getNonce from "./getNonce";
import { v4 as uuidv4 } from "uuid";
function transformTaskData(taskData: any) {
const result = {
@ -8,71 +9,69 @@ function transformTaskData(taskData: any) {
name: taskData.name,
description: taskData.description,
metadata: {
created: taskData.metadata.created
? new Date(taskData.metadata.created)
: new Date(),
created: taskData.metadata.created ? new Date(taskData.metadata.created) : new Date(),
updated: new Date(),
assigned: taskData.metadata.assigned,
progress: taskData.progress,
tags: taskData.metadata.tags
tags: taskData.metadata.tags,
} as any,
relations: taskData.relations,
subTasks: taskData.subTasks,
comments: taskData.comments.map((comment: any) => ({
author: comment.author,
date: new Date(Date.parse(comment.date)),
text: comment.text
}))
text: comment.text,
})),
} as any;
// Add assigned
if (taskData.metadata.assigned) {
result.metadata['assigned'] = taskData.metadata.assigned;
result.metadata["assigned"] = taskData.metadata.assigned;
}
// Add progress
if (taskData.progress > 0) {
result.metadata['progress'] = taskData.progress;
result.metadata["progress"] = taskData.progress;
}
// Add tags
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
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) {
result.metadata['started'] = new Date(Date.parse(taskData.metadata.started));
result.metadata["started"] = new Date(Date.parse(taskData.metadata.started));
}
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;
}
export default class KanbnTaskPanel {
private static readonly viewType = 'react';
private static readonly viewType = "react";
private static panels: Record<string, KanbnTaskPanel> = {};
private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: string;
private readonly _workspacePath: string;
private readonly _kanbn: typeof import('@basementuniverse/kanbn/src/main');
private _taskId: string|null;
private _columnName: string|null;
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
private readonly _panelUuid: string;
private _taskId: string | null;
private _columnName: string | null;
private _disposables: vscode.Disposable[] = [];
public static async show(
extensionPath: string,
workspacePath: string,
kanbn: typeof import('@basementuniverse/kanbn/src/main'),
taskId: string|null,
columnName: string|null
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
taskId: string | null,
columnName: string | null
) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
@ -95,9 +94,9 @@ export default class KanbnTaskPanel {
extensionPath: string,
workspacePath: string,
column: vscode.ViewColumn,
kanbn: typeof import('@basementuniverse/kanbn/src/main'),
taskId: string|null,
columnName: string|null,
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
taskId: string | null,
columnName: string | null,
panelUuid: string
) {
this._extensionPath = extensionPath;
@ -108,7 +107,7 @@ export default class KanbnTaskPanel {
this._panelUuid = panelUuid;
// 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
enableScripts: true,
@ -117,19 +116,19 @@ export default class KanbnTaskPanel {
// Restrict the webview to only loading content from allowed paths
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._extensionPath, 'node_modules', 'vscode-codicons', 'dist'))
]
vscode.Uri.file(path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
],
});
(this._panel as any).iconPath = {
light: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'task_light.svg')),
dark: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'task_dark.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")),
};
// Set the webview's title to the kanbn task name
if (this._taskId !== null) {
this._kanbn.getTask(this._taskId).then(task => {
this._kanbn.getTask(this._taskId).then((task) => {
this._panel.title = task.name;
});
}
@ -142,58 +141,61 @@ export default class KanbnTaskPanel {
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
// Handle messages from the webview
this._panel.webview.onDidReceiveMessage(async message => {
switch (message.command) {
this._panel.webview.onDidReceiveMessage(
async (message) => {
switch (message.command) {
// Display error message
case "error":
vscode.window.showErrorMessage(message.text);
return;
// Display error message
case 'error':
vscode.window.showErrorMessage(message.text);
return;
// Update the task webview panel title
case "kanbn.updatePanelTitle":
this._panel.title = message.title;
return;
// Update the task webview panel title
case 'kanbn.updatePanelTitle':
this._panel.title = message.title;
return;
// Create a task
case 'kanbn.create':
await this._kanbn.createTask(transformTaskData(message.taskData), message.taskData.column);
KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id;
KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column;
KanbnTaskPanel.panels[message.panelUuid].update();
if (vscode.workspace.getConfiguration('kanbn').get('showTaskNotifications')) {
vscode.window.showInformationMessage(`Created task '${message.taskData.name}'.`);
}
return;
// Update a task
case 'kanbn.update':
await this._kanbn.updateTask(message.taskId, transformTaskData(message.taskData), message.taskData.column);
KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id;
KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column;
KanbnTaskPanel.panels[message.panelUuid].update();
if (vscode.workspace.getConfiguration('kanbn').get('showTaskNotifications')) {
vscode.window.showInformationMessage(`Updated task '${message.taskData.name}'.`);
}
return;
// Delete a task and close the webview panel
case 'kanbn.delete':
vscode.window.showInformationMessage(`Delete task '${message.taskData.name}'?`, 'Yes', 'No').then(
async value => {
if (value === 'Yes') {
await this._kanbn.deleteTask(message.taskId, true);
KanbnTaskPanel.panels[message.panelUuid].dispose();
delete KanbnTaskPanel.panels[message.panelUuid];
if (vscode.workspace.getConfiguration('kanbn').get('showTaskNotifications')) {
vscode.window.showInformationMessage(`Deleted task '${message.taskData.name}'.`);
}
}
// Create a task
case "kanbn.create":
await this._kanbn.createTask(transformTaskData(message.taskData), message.taskData.column);
KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id;
KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column;
KanbnTaskPanel.panels[message.panelUuid].update();
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Created task '${message.taskData.name}'.`);
}
);
return;
}
}, null, this._disposables);
return;
// Update a task
case "kanbn.update":
await this._kanbn.updateTask(message.taskId, transformTaskData(message.taskData), message.taskData.column);
KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id;
KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column;
KanbnTaskPanel.panels[message.panelUuid].update();
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Updated task '${message.taskData.name}'.`);
}
return;
// Delete a task and close the webview panel
case "kanbn.delete":
vscode.window
.showInformationMessage(`Delete task '${message.taskData.name}'?`, "Yes", "No")
.then(async (value) => {
if (value === "Yes") {
await this._kanbn.deleteTask(message.taskId, true);
KanbnTaskPanel.panels[message.panelUuid].dispose();
delete KanbnTaskPanel.panels[message.panelUuid];
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
vscode.window.showInformationMessage(`Deleted task '${message.taskData.name}'.`);
}
}
});
return;
}
},
null,
this._disposables
);
}
public dispose() {
@ -216,19 +218,17 @@ export default class KanbnTaskPanel {
}
let tasks: any[];
try {
tasks = (await this._kanbn.loadAllTrackedTasks(index)).map(
task => ({
uuid: uuidv4(),
...this._kanbn.hydrateTask(index, task)
})
);
tasks = (await this._kanbn.loadAllTrackedTasks(index)).map((task) => ({
uuid: uuidv4(),
...this._kanbn.hydrateTask(index, task),
}));
} catch (error) {
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
return;
}
let task = null;
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
@ -238,32 +238,32 @@ export default class KanbnTaskPanel {
// Send task data to the webview
this._panel.webview.postMessage({
type: 'task',
type: "task",
index,
task,
tasks,
columnName: this._columnName,
dateFormat: this._kanbn.getDateFormat(index),
panelUuid: this._panelUuid
panelUuid: this._panelUuid,
});
}
private _getHtmlForWebview() {
const manifest = require(path.join(this._extensionPath, 'build', 'asset-manifest.json'));
const mainScript = manifest['main.js'];
const mainStyle = manifest['main.css'];
const scriptUri = vscode.Uri
.file(path.join(this._extensionPath, 'build', mainScript))
.with({ scheme: 'vscode-resource' });
const styleUri = vscode.Uri
.file(path.join(this._extensionPath, 'build', mainStyle))
.with({ scheme: 'vscode-resource' });
const customStyleUri = vscode.Uri
.file(path.join(this._workspacePath, this._kanbn.getFolderName(), 'board.css'))
.with({ scheme: 'vscode-resource' });
const codiconsUri = vscode.Uri
.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css'))
.with({ scheme: 'vscode-resource' });
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
const mainScript = manifest["main.js"];
const mainStyle = manifest["main.css"];
const scriptUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainScript)).with({
scheme: "vscode-resource",
});
const styleUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainStyle)).with({
scheme: "vscode-resource",
});
const customStyleUri = vscode.Uri.file(
path.join(this._workspacePath, this._kanbn.getFolderName(), "board.css")
).with({ scheme: "vscode-resource" });
const codiconsUri = vscode.Uri.file(
path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
).with({ scheme: "vscode-resource" });
// Use a nonce to whitelist which scripts can be run
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="${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:;">
<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>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
@ -289,12 +289,3 @@ export default class KanbnTaskPanel {
</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,141 +1,129 @@
import * as vscode from 'vscode';
import KanbnStatusBarItem from './KanbnStatusBarItem';
import KanbnBoardPanel from './KanbnBoardPanel';
import KanbnBurndownPanel from './KanbnBurndownPanel';
import KanbnTaskPanel from './KanbnTaskPanel';
import * as vscode from "vscode";
import KanbnStatusBarItem from "./KanbnStatusBarItem";
import KanbnBoardPanel from "./KanbnBoardPanel";
import KanbnBurndownPanel from "./KanbnBurndownPanel";
import KanbnTaskPanel from "./KanbnTaskPanel";
let kanbnStatusBarItem: KanbnStatusBarItem;
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
// bar item is clicked in a workspace where Kanbn isn't already initialised.
context.subscriptions.push(vscode.commands.registerCommand('kanbn.init', async () => {
// If no workspace folder is opened, we can't initialise kanbn
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage('You need to open a workspace before initialising Kanbn.');
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import('@basementuniverse/kanbn/src/main');
// If kanbn is already initialised, get the project name
let projectName = '';
if (await kanbn.initialised()) {
projectName = (await kanbn.getIndex()).name;
}
// Prompt for a new project name
const newProjectName = await vscode.window.showInputBox({
value: projectName,
placeHolder: 'The project name.',
validateInput: text => {
return text.length < 1 ? 'The project name cannot be empty.' : null;
context.subscriptions.push(
vscode.commands.registerCommand("kanbn.init", async () => {
// If no workspace folder is opened, we can't initialise kanbn
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before initialising Kanbn.");
return;
}
});
// If the input prompt wasn't cancelled, initialise kanbn
if (newProjectName !== undefined) {
await kanbn.initialise({
name: newProjectName
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// If kanbn is already initialised, get the project name
let projectName = "";
if (await kanbn.initialised()) {
projectName = (await kanbn.getIndex()).name;
}
// Prompt for a new project name
const newProjectName = await vscode.window.showInputBox({
value: projectName,
placeHolder: "The project name.",
validateInput: (text) => {
return text.length < 1 ? "The project name cannot be empty." : null;
},
});
vscode.window.showInformationMessage(`Initialised Kanbn project '${newProjectName}'.`);
KanbnBoardPanel.update();
}
kanbnStatusBarItem.update();
}));
// If the input prompt wasn't cancelled, initialise kanbn
if (newProjectName !== undefined) {
await kanbn.initialise({
name: newProjectName,
});
vscode.window.showInformationMessage(`Initialised Kanbn project '${newProjectName}'.`);
KanbnBoardPanel.update();
}
kanbnStatusBarItem.update();
})
);
// 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.
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 (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before viewing the Kanbn board.");
return;
}
// If no workspace folder is opened, we can't open the kanbn board
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage('You need to open a workspace before viewing the Kanbn board.');
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import('@basementuniverse/kanbn/src/main');
// If kanbn is initialised, view the kanbn board
if (await kanbn.initialised()) {
KanbnBoardPanel.createOrShow(
context.extensionPath,
vscode.workspace.workspaceFolders[0].uri.fsPath,
kanbn
);
KanbnBoardPanel.update();
} else {
vscode.window.showErrorMessage('You need to initialise Kanbn before viewing the Kanbn board.');
}
kanbnStatusBarItem.update();
}));
// If kanbn is initialised, view the kanbn board
if (await kanbn.initialised()) {
KanbnBoardPanel.createOrShow(context.extensionPath, vscode.workspace.workspaceFolders[0].uri.fsPath, kanbn);
KanbnBoardPanel.update();
} else {
vscode.window.showErrorMessage("You need to initialise Kanbn before viewing the Kanbn board.");
}
kanbnStatusBarItem.update();
})
);
// 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 (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before adding a new task.");
return;
}
// If no workspace folder is opened, we can't add a new task
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage('You need to open a workspace before adding a new task.');
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import('@basementuniverse/kanbn/src/main');
// If kanbn is initialised, open the task webview
if (await kanbn.initialised()) {
KanbnTaskPanel.show(
context.extensionPath,
vscode.workspace.workspaceFolders[0].uri.fsPath,
kanbn,
null,
null
);
} else {
vscode.window.showErrorMessage('You need to initialise Kanbn before adding a new task.');
}
}));
// If kanbn is initialised, open the task webview
if (await kanbn.initialised()) {
KanbnTaskPanel.show(context.extensionPath, vscode.workspace.workspaceFolders[0].uri.fsPath, kanbn, null, null);
} else {
vscode.window.showErrorMessage("You need to initialise Kanbn before adding a new task.");
}
})
);
// 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 (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage("You need to open a workspace before viewing the burndown chart.");
return;
}
// If no workspace folder is opened, we can't open the burndown chart
if (vscode.workspace.workspaceFolders === undefined) {
vscode.window.showErrorMessage('You need to open a workspace before viewing the burndown chart.');
return;
}
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import("@basementuniverse/kanbn/src/main");
// Set the node process directory and import kanbn
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
const kanbn = await import('@basementuniverse/kanbn/src/main');
// If kanbn is initialised, view the burndown chart
if (await kanbn.initialised()) {
KanbnBurndownPanel.createOrShow(
context.extensionPath,
vscode.workspace.workspaceFolders[0].uri.fsPath,
kanbn
);
KanbnBurndownPanel.update();
} else {
vscode.window.showErrorMessage('You need to initialise Kanbn before viewing the burndown chart.');
}
kanbnStatusBarItem.update();
}));
// If kanbn is initialised, view the burndown chart
if (await kanbn.initialised()) {
KanbnBurndownPanel.show(context.extensionPath, vscode.workspace.workspaceFolders[0].uri.fsPath, kanbn);
KanbnBurndownPanel.updateAll();
} else {
vscode.window.showErrorMessage("You need to initialise Kanbn before viewing the burndown chart.");
}
kanbnStatusBarItem.update();
})
);
// If a workspace folder is open, add a status bar item and start watching for file changes
if (vscode.workspace.workspaceFolders !== undefined) {
// Set the node process directory and import kanbn
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
kanbnStatusBarItem = new KanbnStatusBarItem(context, kanbn);
@ -144,18 +132,18 @@ export async function activate(context: vscode.ExtensionContext) {
// Initialise file watcher
const uri = vscode.workspace.workspaceFolders[0].uri.fsPath;
const fileWatcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(
uri,
`${kanbn.getFolderName()}/**.*`
));
const fileWatcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(uri, `${kanbn.getFolderName()}/**.*`)
);
fileWatcher.onDidChange(() => {
kanbnStatusBarItem.update();
KanbnBoardPanel.update();
KanbnBurndownPanel.updateAll();
});
}
// Handle configuration changes
vscode.workspace.onDidChangeConfiguration(e => {
vscode.workspace.onDidChangeConfiguration((e) => {
kanbnStatusBarItem.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;
}