Add burndown panel
1
.gitignore
vendored
@ -10,7 +10,6 @@
|
||||
/build
|
||||
|
||||
# misc
|
||||
/resources
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
|
168
ext-src/KanbnBurndownPanel.ts
Normal file
@ -0,0 +1,168 @@
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export default class KanbnBurndownPanel {
|
||||
public static currentPanel: KanbnBurndownPanel | undefined;
|
||||
|
||||
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 _disposables: vscode.Disposable[] = [];
|
||||
|
||||
public static createOrShow(
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
KanbnBurndownPanel.currentPanel._panel.webview.postMessage({
|
||||
type: 'burndown',
|
||||
index,
|
||||
dateFormat: KanbnBurndownPanel.currentPanel._kanbn.getDateFormat(index)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(
|
||||
extensionPath: string,
|
||||
workspacePath: string,
|
||||
column: vscode.ViewColumn,
|
||||
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(KanbnBurndownPanel.viewType, 'Burndown Chart', column, {
|
||||
// Enable javascript in the webview
|
||||
enableScripts: true,
|
||||
|
||||
// Retain state even when hidden
|
||||
retainContextWhenHidden: true,
|
||||
|
||||
// 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._workspacePath, this._kanbn.getFolderName())),
|
||||
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', 'burndown_light.svg')),
|
||||
dark: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'burndown_dark.svg'))
|
||||
};
|
||||
|
||||
// Set the webview's title to the kanbn project name
|
||||
this._kanbn.getIndex().then(index => {
|
||||
this._panel.title = index.name;
|
||||
});
|
||||
|
||||
// Set the webview's initial html content
|
||||
this._panel.webview.html = this._getHtmlForWebview();
|
||||
|
||||
// Listen for when the panel is disposed
|
||||
// This happens when the user closes the panel or when the panel is closed programatically
|
||||
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
||||
|
||||
// Handle messages from the webview
|
||||
this._panel.webview.onDidReceiveMessage(async message => {
|
||||
switch (message.command) {
|
||||
|
||||
// Display error message
|
||||
case 'error':
|
||||
vscode.window.showErrorMessage(message.text);
|
||||
return;
|
||||
}
|
||||
}, null, this._disposables);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
KanbnBurndownPanel.currentPanel = undefined;
|
||||
|
||||
// Clean up our resources
|
||||
this._panel.dispose();
|
||||
while (this._disposables.length) {
|
||||
const x = this._disposables.pop();
|
||||
if (x) {
|
||||
x.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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' });
|
||||
|
||||
// Use a nonce to whitelist which scripts can be run
|
||||
const nonce = getNonce();
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<title>Kanbn Board</title>
|
||||
<link rel="stylesheet" type="text/css" href="${styleUri}">
|
||||
<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' })}/">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
</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
resources/burndown_dark.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g fill="#ffffff"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 14H15v-1H2V0H1v13.5l.5.5zM3 11.5v-8l.5-.5h2l.5.5v8l-.5.5h-2l-.5-.5zm2-.5V4H4v7h1zm6-9.5v10l.5.5h2l.5-.5v-10l-.5-.5h-2l-.5.5zm2 .5v9h-1V2h1zm-6 9.5v-6l.5-.5h2l.5.5v6l-.5.5h-2l-.5-.5zm2-.5V6H8v5h1z"/></g><rect x="0" y="0" width="16" height="16" fill="rgba(0, 0, 0, 0)" /></svg>
|
After Width: | Height: | Size: 610 B |
1
resources/burndown_light.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g fill="#000000"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 14H15v-1H2V0H1v13.5l.5.5zM3 11.5v-8l.5-.5h2l.5.5v8l-.5.5h-2l-.5-.5zm2-.5V4H4v7h1zm6-9.5v10l.5.5h2l.5-.5v-10l-.5-.5h-2l-.5.5zm2 .5v9h-1V2h1zm-6 9.5v-6l.5-.5h2l.5.5v6l-.5.5h-2l-.5-.5zm2-.5V6H8v5h1z"/></g><rect x="0" y="0" width="16" height="16" fill="rgba(0, 0, 0, 0)" /></svg>
|
After Width: | Height: | Size: 610 B |
BIN
resources/kanbn.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
1
resources/project_dark.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g fill="#ffffff"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 14h12V2H2v12zM3 3h2v10H3V3zm6 0H7v6h2V3zm2 0h2v8h-2V3z"/></g><rect x="0" y="0" width="16" height="16" fill="rgba(0, 0, 0, 0)" /></svg>
|
After Width: | Height: | Size: 512 B |
1
resources/project_light.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" preserveAspectRatio="xMidYMid meet" viewBox="0 0 16 16" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g fill="#000000"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 1h13l.5.5v13l-.5.5h-13l-.5-.5v-13l.5-.5zM2 14h12V2H2v12zM3 3h2v10H3V3zm6 0H7v6h2V3zm2 0h2v8h-2V3z"/></g><rect x="0" y="0" width="16" height="16" fill="rgba(0, 0, 0, 0)" /></svg>
|
After Width: | Height: | Size: 512 B |
13
resources/task_dark.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st1{fill:none;stroke:#ffffff;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<polyline class="st0" points="25,20 21,24 19,22 "/>
|
||||
<circle class="st0" cx="22" cy="22" r="7"/>
|
||||
<polyline class="st0" points="19,3 19,9 25,9 19,3 7,3 7,29 22,29 "/>
|
||||
<line class="st0" x1="25" y1="9" x2="25" y2="15.7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 817 B |
13
resources/task_light.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st1{fill:none;stroke:#000000;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<polyline class="st0" points="25,20 21,24 19,22 "/>
|
||||
<circle class="st0" cx="22" cy="22" r="7"/>
|
||||
<polyline class="st0" points="19,3 19,9 25,9 19,3 7,3 7,29 22,29 "/>
|
||||
<line class="st0" x1="25" y1="9" x2="25" y2="15.7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 817 B |
@ -1,4 +1,5 @@
|
||||
import Board from './Board';
|
||||
import Burndown from './Burndown';
|
||||
import TaskEditor from './TaskEditor';
|
||||
import React, { useState } from "react";
|
||||
import VSCodeApi from "./VSCodeApi";
|
||||
@ -94,6 +95,11 @@ function App() {
|
||||
vscode={vscode}
|
||||
/>
|
||||
}
|
||||
{
|
||||
type === 'burndown' &&
|
||||
<Burndown
|
||||
/>
|
||||
}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
9
src/Burndown.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
const Burndown = () => {
|
||||
return (
|
||||
<div>burndown chart</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Burndown;
|