Add burndown panel
1
.gitignore
vendored
@ -10,7 +10,6 @@
|
|||||||
/build
|
/build
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
/resources
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
.env.local
|
||||||
.env.development.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 Board from './Board';
|
||||||
|
import Burndown from './Burndown';
|
||||||
import TaskEditor from './TaskEditor';
|
import TaskEditor from './TaskEditor';
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import VSCodeApi from "./VSCodeApi";
|
import VSCodeApi from "./VSCodeApi";
|
||||||
@ -94,6 +95,11 @@ function App() {
|
|||||||
vscode={vscode}
|
vscode={vscode}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
type === 'burndown' &&
|
||||||
|
<Burndown
|
||||||
|
/>
|
||||||
|
}
|
||||||
</React.Fragment>
|
</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;
|