Allow opening multiple burndown panels
This commit is contained in:
parent
0d8508dd19
commit
cb8647ba5a
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
8
ext-src/getNonce.ts
Normal 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;
|
||||
}
|
11
src/App.tsx
11
src/App.tsx
@ -30,7 +30,9 @@ function App() {
|
||||
const [burndownData, setBurndownData] = useState({ series: [] });
|
||||
|
||||
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) {
|
||||
case 'index':
|
||||
setName(event.data.index.name);
|
||||
@ -66,7 +68,11 @@ function App() {
|
||||
case 'burndown':
|
||||
setName(event.data.index.name);
|
||||
setTasks(tasks);
|
||||
setSprints(event.data.index.options.sprints || []);
|
||||
setSprints(
|
||||
'sprints' in event.data.index.options
|
||||
? event.data.index.options.sprints
|
||||
: []
|
||||
);
|
||||
setBurndownData(event.data.burndownData);
|
||||
break;
|
||||
}
|
||||
@ -111,6 +117,7 @@ function App() {
|
||||
tasks={tasks}
|
||||
sprints={sprints}
|
||||
burndownData={burndownData}
|
||||
vscode={vscode}
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
|
100
src/Burndown.tsx
100
src/Burndown.tsx
@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import { LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts';
|
||||
import React, { useState } from 'react';
|
||||
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,
|
||||
tasks: Record<string, KanbnTask>,
|
||||
sprints: KanbnSprint[],
|
||||
@ -15,8 +17,14 @@ const Burndown = ({ name, tasks, sprints, burndownData }: {
|
||||
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 = [
|
||||
{name: 'one', uv: 20},
|
||||
{name: 'two', uv: 80},
|
||||
@ -26,14 +34,82 @@ const Burndown = ({ name, tasks, sprints, burndownData }: {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>burndown chart</div>
|
||||
<LineChart width={600} height={300} data={data} margin={{ top: 5, right: 20, bottom: 5, left: 0 }}>
|
||||
<Line type="monotone" dataKey="uv" stroke="#8884d8" />
|
||||
<CartesianGrid stroke="#ccc" strokeDasharray="5 5" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
</LineChart>
|
||||
<div className="kanbn-header">
|
||||
<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" />
|
||||
<CartesianGrid stroke="#ccc" strokeDasharray="5 5" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ body.vscode-high-contrast {
|
||||
|
||||
.kanbn-header-name p {
|
||||
display: inline-block;
|
||||
flex: 1;
|
||||
flex: 2;
|
||||
margin: 0;
|
||||
padding: 4px 0;
|
||||
}
|
||||
@ -42,6 +42,8 @@ body.vscode-high-contrast {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -340,12 +342,13 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
|
||||
margin: 8px 0;
|
||||
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-task-editor-field-input[type=date] {
|
||||
font-family: var(--vscode-font-family);
|
||||
font-size: var(--vscode-font-size);
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user