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 path from "path";
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from "vscode";
|
||||||
import KanbnTaskPanel from './KanbnTaskPanel';
|
import getNonce from "./getNonce";
|
||||||
import KanbnBurndownPanel from './KanbnBurndownPanel';
|
import KanbnTaskPanel from "./KanbnTaskPanel";
|
||||||
|
import KanbnBurndownPanel from "./KanbnBurndownPanel";
|
||||||
|
|
||||||
export default class KanbnBoardPanel {
|
export default class KanbnBoardPanel {
|
||||||
public static currentPanel: KanbnBoardPanel | undefined;
|
public static currentPanel: KanbnBoardPanel | undefined;
|
||||||
|
|
||||||
private static readonly viewType = 'react';
|
private static readonly viewType = "react";
|
||||||
|
|
||||||
private readonly _panel: vscode.WebviewPanel;
|
private readonly _panel: vscode.WebviewPanel;
|
||||||
private readonly _extensionPath: string;
|
private readonly _extensionPath: string;
|
||||||
private readonly _workspacePath: string;
|
private readonly _workspacePath: string;
|
||||||
private readonly _kanbn: typeof import('@basementuniverse/kanbn/src/main');
|
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
|
||||||
private _disposables: vscode.Disposable[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
public static createOrShow(
|
public static createOrShow(
|
||||||
extensionPath: string,
|
extensionPath: string,
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
kanbn: typeof import('@basementuniverse/kanbn/src/main')
|
kanbn: typeof import("@basementuniverse/kanbn/src/main")
|
||||||
) {
|
) {
|
||||||
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
|
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
|
||||||
|
|
||||||
@ -45,23 +46,23 @@ export default class KanbnBoardPanel {
|
|||||||
}
|
}
|
||||||
let tasks: any[];
|
let tasks: any[];
|
||||||
try {
|
try {
|
||||||
tasks = (await KanbnBoardPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map(
|
tasks = (await KanbnBoardPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map((task) =>
|
||||||
task => KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task)
|
KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task)
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
|
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
KanbnBoardPanel.currentPanel._panel.webview.postMessage({
|
KanbnBoardPanel.currentPanel._panel.webview.postMessage({
|
||||||
type: 'index',
|
type: "index",
|
||||||
index,
|
index,
|
||||||
tasks,
|
tasks,
|
||||||
hiddenColumns: index.options.hiddenColumns ?? [],
|
hiddenColumns: index.options.hiddenColumns ?? [],
|
||||||
startedColumns: index.options.startedColumns ?? [],
|
startedColumns: index.options.startedColumns ?? [],
|
||||||
completedColumns: index.options.completedColumns ?? [],
|
completedColumns: index.options.completedColumns ?? [],
|
||||||
dateFormat: KanbnBoardPanel.currentPanel._kanbn.getDateFormat(index),
|
dateFormat: KanbnBoardPanel.currentPanel._kanbn.getDateFormat(index),
|
||||||
showBurndownButton: vscode.workspace.getConfiguration('kanbn').get('showBurndownButton'),
|
showBurndownButton: vscode.workspace.getConfiguration("kanbn").get("showBurndownButton"),
|
||||||
showSprintButton: vscode.workspace.getConfiguration('kanbn').get('showSprintButton')
|
showSprintButton: vscode.workspace.getConfiguration("kanbn").get("showSprintButton"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,14 +71,14 @@ export default class KanbnBoardPanel {
|
|||||||
extensionPath: string,
|
extensionPath: string,
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
column: vscode.ViewColumn,
|
column: vscode.ViewColumn,
|
||||||
kanbn: typeof import('@basementuniverse/kanbn/src/main')
|
kanbn: typeof import("@basementuniverse/kanbn/src/main")
|
||||||
) {
|
) {
|
||||||
this._extensionPath = extensionPath;
|
this._extensionPath = extensionPath;
|
||||||
this._workspacePath = workspacePath;
|
this._workspacePath = workspacePath;
|
||||||
this._kanbn = kanbn;
|
this._kanbn = kanbn;
|
||||||
|
|
||||||
// Create and show a new webview panel
|
// Create and show a new webview panel
|
||||||
this._panel = vscode.window.createWebviewPanel(KanbnBoardPanel.viewType, 'Kanbn Board', column, {
|
this._panel = vscode.window.createWebviewPanel(KanbnBoardPanel.viewType, "Kanbn Board", column, {
|
||||||
// Enable javascript in the webview
|
// Enable javascript in the webview
|
||||||
enableScripts: true,
|
enableScripts: true,
|
||||||
|
|
||||||
@ -86,18 +87,18 @@ export default class KanbnBoardPanel {
|
|||||||
|
|
||||||
// Restrict the webview to only loading content from allowed paths
|
// Restrict the webview to only loading content from allowed paths
|
||||||
localResourceRoots: [
|
localResourceRoots: [
|
||||||
vscode.Uri.file(path.join(this._extensionPath, 'build')),
|
vscode.Uri.file(path.join(this._extensionPath, "build")),
|
||||||
vscode.Uri.file(path.join(this._workspacePath, this._kanbn.getFolderName())),
|
vscode.Uri.file(path.join(this._workspacePath, this._kanbn.getFolderName())),
|
||||||
vscode.Uri.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist'))
|
vscode.Uri.file(path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
(this._panel as any).iconPath = {
|
(this._panel as any).iconPath = {
|
||||||
light: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'project_light.svg')),
|
light: vscode.Uri.file(path.join(this._extensionPath, "resources", "project_light.svg")),
|
||||||
dark: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'project_dark.svg'))
|
dark: vscode.Uri.file(path.join(this._extensionPath, "resources", "project_dark.svg")),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the webview's title to the kanbn project name
|
// Set the webview's title to the kanbn project name
|
||||||
this._kanbn.getIndex().then(index => {
|
this._kanbn.getIndex().then((index) => {
|
||||||
this._panel.title = index.name;
|
this._panel.title = index.name;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -109,73 +110,65 @@ export default class KanbnBoardPanel {
|
|||||||
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
||||||
|
|
||||||
// Handle messages from the webview
|
// Handle messages from the webview
|
||||||
this._panel.webview.onDidReceiveMessage(async message => {
|
this._panel.webview.onDidReceiveMessage(
|
||||||
switch (message.command) {
|
async (message) => {
|
||||||
|
switch (message.command) {
|
||||||
|
// Display error message
|
||||||
|
case "error":
|
||||||
|
vscode.window.showErrorMessage(message.text);
|
||||||
|
return;
|
||||||
|
|
||||||
// Display error message
|
case "kanbn.task":
|
||||||
case 'error':
|
KanbnTaskPanel.show(
|
||||||
vscode.window.showErrorMessage(message.text);
|
this._extensionPath,
|
||||||
return;
|
this._workspacePath,
|
||||||
|
this._kanbn,
|
||||||
|
message.taskId,
|
||||||
|
message.columnName
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
|
||||||
case 'kanbn.task':
|
// Move a task
|
||||||
KanbnTaskPanel.show(
|
case "kanbn.move":
|
||||||
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) {
|
|
||||||
try {
|
try {
|
||||||
await kanbn.sprint(newSprintName, '', new Date());
|
await kanbn.moveTask(message.task, message.columnName, message.position);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
vscode.window.showErrorMessage(e.message);
|
vscode.window.showErrorMessage(e.message);
|
||||||
}
|
}
|
||||||
}
|
return;
|
||||||
return;
|
|
||||||
}
|
// Create a task
|
||||||
}, null, this._disposables);
|
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() {
|
public dispose() {
|
||||||
@ -192,21 +185,21 @@ export default class KanbnBoardPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _getHtmlForWebview() {
|
private _getHtmlForWebview() {
|
||||||
const manifest = require(path.join(this._extensionPath, 'build', 'asset-manifest.json'));
|
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
|
||||||
const mainScript = manifest['main.js'];
|
const mainScript = manifest["main.js"];
|
||||||
const mainStyle = manifest['main.css'];
|
const mainStyle = manifest["main.css"];
|
||||||
const scriptUri = vscode.Uri
|
const scriptUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainScript)).with({
|
||||||
.file(path.join(this._extensionPath, 'build', mainScript))
|
scheme: "vscode-resource",
|
||||||
.with({ scheme: 'vscode-resource' });
|
});
|
||||||
const styleUri = vscode.Uri
|
const styleUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainStyle)).with({
|
||||||
.file(path.join(this._extensionPath, 'build', mainStyle))
|
scheme: "vscode-resource",
|
||||||
.with({ scheme: 'vscode-resource' });
|
});
|
||||||
const customStyleUri = vscode.Uri
|
const customStyleUri = vscode.Uri.file(
|
||||||
.file(path.join(this._workspacePath, this._kanbn.getFolderName(), 'board.css'))
|
path.join(this._workspacePath, this._kanbn.getFolderName(), "board.css")
|
||||||
.with({ scheme: 'vscode-resource' });
|
).with({ scheme: "vscode-resource" });
|
||||||
const codiconsUri = vscode.Uri
|
const codiconsUri = vscode.Uri.file(
|
||||||
.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css'))
|
path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
|
||||||
.with({ scheme: 'vscode-resource' });
|
).with({ scheme: "vscode-resource" });
|
||||||
|
|
||||||
// Use a nonce to whitelist which scripts can be run
|
// Use a nonce to whitelist which scripts can be run
|
||||||
const nonce = getNonce();
|
const nonce = getNonce();
|
||||||
@ -222,7 +215,7 @@ export default class KanbnBoardPanel {
|
|||||||
<link rel="stylesheet" type="text/css" href="${customStyleUri}">
|
<link rel="stylesheet" type="text/css" href="${customStyleUri}">
|
||||||
<link rel="stylesheet" type="text/css" href="${codiconsUri}">
|
<link rel="stylesheet" type="text/css" href="${codiconsUri}">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
|
||||||
<base href="${vscode.Uri.file(path.join(this._extensionPath, 'build')).with({ scheme: 'vscode-resource' })}/">
|
<base href="${vscode.Uri.file(path.join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
@ -232,12 +225,3 @@ export default class KanbnBoardPanel {
|
|||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNonce() {
|
|
||||||
let text = "";
|
|
||||||
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
for (let i = 0; i < 32; i++) {
|
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
@ -1,73 +1,72 @@
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
|
import getNonce from "./getNonce";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
export default class KanbnBurndownPanel {
|
export default class KanbnBurndownPanel {
|
||||||
public static currentPanel: KanbnBurndownPanel | undefined;
|
|
||||||
|
|
||||||
private static readonly viewType = "react";
|
private static readonly viewType = "react";
|
||||||
|
private static panels: Record<string, KanbnBurndownPanel> = {};
|
||||||
|
|
||||||
private readonly _panel: vscode.WebviewPanel;
|
private readonly _panel: vscode.WebviewPanel;
|
||||||
private readonly _extensionPath: string;
|
private readonly _extensionPath: string;
|
||||||
private readonly _workspacePath: string;
|
private readonly _workspacePath: string;
|
||||||
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
|
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
|
||||||
|
private readonly _panelUuid: string;
|
||||||
private _disposables: vscode.Disposable[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
public static createOrShow(
|
public static async show(
|
||||||
extensionPath: string,
|
extensionPath: string,
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
kanbn: typeof import("@basementuniverse/kanbn/src/main")
|
kanbn: typeof import("@basementuniverse/kanbn/src/main")
|
||||||
) {
|
) {
|
||||||
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
|
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
|
||||||
|
|
||||||
// If we already have a panel, show it, otherwise create a new panel
|
// Create a panel
|
||||||
if (KanbnBurndownPanel.currentPanel) {
|
const panelUuid = uuidv4();
|
||||||
KanbnBurndownPanel.currentPanel._panel.reveal(column);
|
const burndownPanel = new KanbnBurndownPanel(
|
||||||
} else {
|
extensionPath,
|
||||||
KanbnBurndownPanel.currentPanel = new KanbnBurndownPanel(
|
workspacePath,
|
||||||
extensionPath,
|
column || vscode.ViewColumn.One,
|
||||||
workspacePath,
|
kanbn,
|
||||||
column || vscode.ViewColumn.One,
|
panelUuid
|
||||||
kanbn
|
);
|
||||||
);
|
KanbnBurndownPanel.panels[panelUuid] = burndownPanel;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async update() {
|
public static async updateAll() {
|
||||||
if (KanbnBurndownPanel.currentPanel) {
|
const panels = Object.values(KanbnBurndownPanel.panels);
|
||||||
let index: any;
|
if (panels.length === 0) {
|
||||||
try {
|
return;
|
||||||
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),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
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(
|
private constructor(
|
||||||
extensionPath: string,
|
extensionPath: string,
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
column: vscode.ViewColumn,
|
column: vscode.ViewColumn,
|
||||||
kanbn: typeof import("@basementuniverse/kanbn/src/main")
|
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
|
||||||
|
panelUuid: string
|
||||||
) {
|
) {
|
||||||
this._extensionPath = extensionPath;
|
this._extensionPath = extensionPath;
|
||||||
this._workspacePath = workspacePath;
|
this._workspacePath = workspacePath;
|
||||||
this._kanbn = kanbn;
|
this._kanbn = kanbn;
|
||||||
|
this._panelUuid = panelUuid;
|
||||||
|
|
||||||
// Create and show a new webview panel
|
// Create and show a new webview panel
|
||||||
this._panel = vscode.window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, {
|
this._panel = vscode.window.createWebviewPanel(KanbnBurndownPanel.viewType, "Burndown Chart", column, {
|
||||||
@ -117,9 +116,6 @@ export default class KanbnBurndownPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
KanbnBurndownPanel.currentPanel = undefined;
|
|
||||||
|
|
||||||
// Clean up our resources
|
|
||||||
this._panel.dispose();
|
this._panel.dispose();
|
||||||
while (this._disposables.length) {
|
while (this._disposables.length) {
|
||||||
const x = this._disposables.pop();
|
const x = this._disposables.pop();
|
||||||
@ -129,6 +125,15 @@ export default class KanbnBurndownPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _update(index: any, tasks: any[]) {
|
||||||
|
this._panel.webview.postMessage({
|
||||||
|
type: "burndown",
|
||||||
|
index,
|
||||||
|
tasks,
|
||||||
|
dateFormat: this._kanbn.getDateFormat(index),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private _getHtmlForWebview() {
|
private _getHtmlForWebview() {
|
||||||
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
|
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
|
||||||
const mainScript = manifest["main.js"];
|
const mainScript = manifest["main.js"];
|
||||||
@ -170,12 +175,3 @@ export default class KanbnBurndownPanel {
|
|||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNonce() {
|
|
||||||
let text = "";
|
|
||||||
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
for (let i = 0; i < 32; i++) {
|
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import * as path from 'path';
|
import * as path from "path";
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from "vscode";
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import getNonce from "./getNonce";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
function transformTaskData(taskData: any) {
|
function transformTaskData(taskData: any) {
|
||||||
const result = {
|
const result = {
|
||||||
@ -8,71 +9,69 @@ function transformTaskData(taskData: any) {
|
|||||||
name: taskData.name,
|
name: taskData.name,
|
||||||
description: taskData.description,
|
description: taskData.description,
|
||||||
metadata: {
|
metadata: {
|
||||||
created: taskData.metadata.created
|
created: taskData.metadata.created ? new Date(taskData.metadata.created) : new Date(),
|
||||||
? new Date(taskData.metadata.created)
|
|
||||||
: new Date(),
|
|
||||||
updated: new Date(),
|
updated: new Date(),
|
||||||
assigned: taskData.metadata.assigned,
|
assigned: taskData.metadata.assigned,
|
||||||
progress: taskData.progress,
|
progress: taskData.progress,
|
||||||
tags: taskData.metadata.tags
|
tags: taskData.metadata.tags,
|
||||||
} as any,
|
} as any,
|
||||||
relations: taskData.relations,
|
relations: taskData.relations,
|
||||||
subTasks: taskData.subTasks,
|
subTasks: taskData.subTasks,
|
||||||
comments: taskData.comments.map((comment: any) => ({
|
comments: taskData.comments.map((comment: any) => ({
|
||||||
author: comment.author,
|
author: comment.author,
|
||||||
date: new Date(Date.parse(comment.date)),
|
date: new Date(Date.parse(comment.date)),
|
||||||
text: comment.text
|
text: comment.text,
|
||||||
}))
|
})),
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
// Add assigned
|
// Add assigned
|
||||||
if (taskData.metadata.assigned) {
|
if (taskData.metadata.assigned) {
|
||||||
result.metadata['assigned'] = taskData.metadata.assigned;
|
result.metadata["assigned"] = taskData.metadata.assigned;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add progress
|
// Add progress
|
||||||
if (taskData.progress > 0) {
|
if (taskData.progress > 0) {
|
||||||
result.metadata['progress'] = taskData.progress;
|
result.metadata["progress"] = taskData.progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add tags
|
// Add tags
|
||||||
if (taskData.metadata.tags.length) {
|
if (taskData.metadata.tags.length) {
|
||||||
result.metadata['tags'] = taskData.metadata.tags;
|
result.metadata["tags"] = taskData.metadata.tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add due, started and completed dates if present
|
// Add due, started and completed dates if present
|
||||||
if (taskData.metadata.due) {
|
if (taskData.metadata.due) {
|
||||||
result.metadata['due'] = new Date(Date.parse(taskData.metadata.due));
|
result.metadata["due"] = new Date(Date.parse(taskData.metadata.due));
|
||||||
}
|
}
|
||||||
if (taskData.metadata.started) {
|
if (taskData.metadata.started) {
|
||||||
result.metadata['started'] = new Date(Date.parse(taskData.metadata.started));
|
result.metadata["started"] = new Date(Date.parse(taskData.metadata.started));
|
||||||
}
|
}
|
||||||
if (taskData.metadata.completed) {
|
if (taskData.metadata.completed) {
|
||||||
result.metadata['completed'] = new Date(Date.parse(taskData.metadata.completed));
|
result.metadata["completed"] = new Date(Date.parse(taskData.metadata.completed));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class KanbnTaskPanel {
|
export default class KanbnTaskPanel {
|
||||||
private static readonly viewType = 'react';
|
private static readonly viewType = "react";
|
||||||
private static panels: Record<string, KanbnTaskPanel> = {};
|
private static panels: Record<string, KanbnTaskPanel> = {};
|
||||||
|
|
||||||
private readonly _panel: vscode.WebviewPanel;
|
private readonly _panel: vscode.WebviewPanel;
|
||||||
private readonly _extensionPath: string;
|
private readonly _extensionPath: string;
|
||||||
private readonly _workspacePath: string;
|
private readonly _workspacePath: string;
|
||||||
private readonly _kanbn: typeof import('@basementuniverse/kanbn/src/main');
|
private readonly _kanbn: typeof import("@basementuniverse/kanbn/src/main");
|
||||||
private _taskId: string|null;
|
|
||||||
private _columnName: string|null;
|
|
||||||
private readonly _panelUuid: string;
|
private readonly _panelUuid: string;
|
||||||
|
private _taskId: string | null;
|
||||||
|
private _columnName: string | null;
|
||||||
private _disposables: vscode.Disposable[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
public static async show(
|
public static async show(
|
||||||
extensionPath: string,
|
extensionPath: string,
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
kanbn: typeof import('@basementuniverse/kanbn/src/main'),
|
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
|
||||||
taskId: string|null,
|
taskId: string | null,
|
||||||
columnName: string|null
|
columnName: string | null
|
||||||
) {
|
) {
|
||||||
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
|
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
|
||||||
|
|
||||||
@ -95,9 +94,9 @@ export default class KanbnTaskPanel {
|
|||||||
extensionPath: string,
|
extensionPath: string,
|
||||||
workspacePath: string,
|
workspacePath: string,
|
||||||
column: vscode.ViewColumn,
|
column: vscode.ViewColumn,
|
||||||
kanbn: typeof import('@basementuniverse/kanbn/src/main'),
|
kanbn: typeof import("@basementuniverse/kanbn/src/main"),
|
||||||
taskId: string|null,
|
taskId: string | null,
|
||||||
columnName: string|null,
|
columnName: string | null,
|
||||||
panelUuid: string
|
panelUuid: string
|
||||||
) {
|
) {
|
||||||
this._extensionPath = extensionPath;
|
this._extensionPath = extensionPath;
|
||||||
@ -108,7 +107,7 @@ export default class KanbnTaskPanel {
|
|||||||
this._panelUuid = panelUuid;
|
this._panelUuid = panelUuid;
|
||||||
|
|
||||||
// Create and show a new webview panel
|
// Create and show a new webview panel
|
||||||
this._panel = vscode.window.createWebviewPanel(KanbnTaskPanel.viewType, 'New task', column, {
|
this._panel = vscode.window.createWebviewPanel(KanbnTaskPanel.viewType, "New task", column, {
|
||||||
// Enable javascript in the webview
|
// Enable javascript in the webview
|
||||||
enableScripts: true,
|
enableScripts: true,
|
||||||
|
|
||||||
@ -117,19 +116,19 @@ export default class KanbnTaskPanel {
|
|||||||
|
|
||||||
// Restrict the webview to only loading content from allowed paths
|
// Restrict the webview to only loading content from allowed paths
|
||||||
localResourceRoots: [
|
localResourceRoots: [
|
||||||
vscode.Uri.file(path.join(this._extensionPath, 'build')),
|
vscode.Uri.file(path.join(this._extensionPath, "build")),
|
||||||
vscode.Uri.file(path.join(this._workspacePath, this._kanbn.getFolderName())),
|
vscode.Uri.file(path.join(this._workspacePath, this._kanbn.getFolderName())),
|
||||||
vscode.Uri.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist'))
|
vscode.Uri.file(path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist")),
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
(this._panel as any).iconPath = {
|
(this._panel as any).iconPath = {
|
||||||
light: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'task_light.svg')),
|
light: vscode.Uri.file(path.join(this._extensionPath, "resources", "task_light.svg")),
|
||||||
dark: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'task_dark.svg'))
|
dark: vscode.Uri.file(path.join(this._extensionPath, "resources", "task_dark.svg")),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the webview's title to the kanbn task name
|
// Set the webview's title to the kanbn task name
|
||||||
if (this._taskId !== null) {
|
if (this._taskId !== null) {
|
||||||
this._kanbn.getTask(this._taskId).then(task => {
|
this._kanbn.getTask(this._taskId).then((task) => {
|
||||||
this._panel.title = task.name;
|
this._panel.title = task.name;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -142,58 +141,61 @@ export default class KanbnTaskPanel {
|
|||||||
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
||||||
|
|
||||||
// Handle messages from the webview
|
// Handle messages from the webview
|
||||||
this._panel.webview.onDidReceiveMessage(async message => {
|
this._panel.webview.onDidReceiveMessage(
|
||||||
switch (message.command) {
|
async (message) => {
|
||||||
|
switch (message.command) {
|
||||||
|
// Display error message
|
||||||
|
case "error":
|
||||||
|
vscode.window.showErrorMessage(message.text);
|
||||||
|
return;
|
||||||
|
|
||||||
// Display error message
|
// Update the task webview panel title
|
||||||
case 'error':
|
case "kanbn.updatePanelTitle":
|
||||||
vscode.window.showErrorMessage(message.text);
|
this._panel.title = message.title;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Update the task webview panel title
|
// Create a task
|
||||||
case 'kanbn.updatePanelTitle':
|
case "kanbn.create":
|
||||||
this._panel.title = message.title;
|
await this._kanbn.createTask(transformTaskData(message.taskData), message.taskData.column);
|
||||||
return;
|
KanbnTaskPanel.panels[message.panelUuid]._taskId = message.taskData.id;
|
||||||
|
KanbnTaskPanel.panels[message.panelUuid]._columnName = message.taskData.column;
|
||||||
// Create a task
|
KanbnTaskPanel.panels[message.panelUuid].update();
|
||||||
case 'kanbn.create':
|
if (vscode.workspace.getConfiguration("kanbn").get("showTaskNotifications")) {
|
||||||
await this._kanbn.createTask(transformTaskData(message.taskData), message.taskData.column);
|
vscode.window.showInformationMessage(`Created task '${message.taskData.name}'.`);
|
||||||
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}'.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
return;
|
||||||
return;
|
|
||||||
}
|
// Update a task
|
||||||
}, null, this._disposables);
|
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() {
|
public dispose() {
|
||||||
@ -216,19 +218,17 @@ export default class KanbnTaskPanel {
|
|||||||
}
|
}
|
||||||
let tasks: any[];
|
let tasks: any[];
|
||||||
try {
|
try {
|
||||||
tasks = (await this._kanbn.loadAllTrackedTasks(index)).map(
|
tasks = (await this._kanbn.loadAllTrackedTasks(index)).map((task) => ({
|
||||||
task => ({
|
uuid: uuidv4(),
|
||||||
uuid: uuidv4(),
|
...this._kanbn.hydrateTask(index, task),
|
||||||
...this._kanbn.hydrateTask(index, task)
|
}));
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
|
vscode.window.showErrorMessage(error instanceof Error ? error.message : error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let task = null;
|
let task = null;
|
||||||
if (this._taskId) {
|
if (this._taskId) {
|
||||||
task = tasks.find(t => t.id === this._taskId) ?? null;
|
task = tasks.find((t) => t.id === this._taskId) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no columnName is specified, use the first column
|
// If no columnName is specified, use the first column
|
||||||
@ -238,32 +238,32 @@ export default class KanbnTaskPanel {
|
|||||||
|
|
||||||
// Send task data to the webview
|
// Send task data to the webview
|
||||||
this._panel.webview.postMessage({
|
this._panel.webview.postMessage({
|
||||||
type: 'task',
|
type: "task",
|
||||||
index,
|
index,
|
||||||
task,
|
task,
|
||||||
tasks,
|
tasks,
|
||||||
columnName: this._columnName,
|
columnName: this._columnName,
|
||||||
dateFormat: this._kanbn.getDateFormat(index),
|
dateFormat: this._kanbn.getDateFormat(index),
|
||||||
panelUuid: this._panelUuid
|
panelUuid: this._panelUuid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getHtmlForWebview() {
|
private _getHtmlForWebview() {
|
||||||
const manifest = require(path.join(this._extensionPath, 'build', 'asset-manifest.json'));
|
const manifest = require(path.join(this._extensionPath, "build", "asset-manifest.json"));
|
||||||
const mainScript = manifest['main.js'];
|
const mainScript = manifest["main.js"];
|
||||||
const mainStyle = manifest['main.css'];
|
const mainStyle = manifest["main.css"];
|
||||||
const scriptUri = vscode.Uri
|
const scriptUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainScript)).with({
|
||||||
.file(path.join(this._extensionPath, 'build', mainScript))
|
scheme: "vscode-resource",
|
||||||
.with({ scheme: 'vscode-resource' });
|
});
|
||||||
const styleUri = vscode.Uri
|
const styleUri = vscode.Uri.file(path.join(this._extensionPath, "build", mainStyle)).with({
|
||||||
.file(path.join(this._extensionPath, 'build', mainStyle))
|
scheme: "vscode-resource",
|
||||||
.with({ scheme: 'vscode-resource' });
|
});
|
||||||
const customStyleUri = vscode.Uri
|
const customStyleUri = vscode.Uri.file(
|
||||||
.file(path.join(this._workspacePath, this._kanbn.getFolderName(), 'board.css'))
|
path.join(this._workspacePath, this._kanbn.getFolderName(), "board.css")
|
||||||
.with({ scheme: 'vscode-resource' });
|
).with({ scheme: "vscode-resource" });
|
||||||
const codiconsUri = vscode.Uri
|
const codiconsUri = vscode.Uri.file(
|
||||||
.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css'))
|
path.join(this._extensionPath, "node_modules", "vscode-codicons", "dist", "codicon.css")
|
||||||
.with({ scheme: 'vscode-resource' });
|
).with({ scheme: "vscode-resource" });
|
||||||
|
|
||||||
// Use a nonce to whitelist which scripts can be run
|
// Use a nonce to whitelist which scripts can be run
|
||||||
const nonce = getNonce();
|
const nonce = getNonce();
|
||||||
@ -279,7 +279,7 @@ export default class KanbnTaskPanel {
|
|||||||
<link rel="stylesheet" type="text/css" href="${customStyleUri}">
|
<link rel="stylesheet" type="text/css" href="${customStyleUri}">
|
||||||
<link rel="stylesheet" type="text/css" href="${codiconsUri}">
|
<link rel="stylesheet" type="text/css" href="${codiconsUri}">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}'; font-src vscode-resource:; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
|
||||||
<base href="${vscode.Uri.file(path.join(this._extensionPath, 'build')).with({ scheme: 'vscode-resource' })}/">
|
<base href="${vscode.Uri.file(path.join(this._extensionPath, "build")).with({ scheme: "vscode-resource" })}/">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
@ -289,12 +289,3 @@ export default class KanbnTaskPanel {
|
|||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNonce() {
|
|
||||||
let text = "";
|
|
||||||
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
for (let i = 0; i < 32; i++) {
|
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
@ -1,141 +1,129 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from "vscode";
|
||||||
import KanbnStatusBarItem from './KanbnStatusBarItem';
|
import KanbnStatusBarItem from "./KanbnStatusBarItem";
|
||||||
import KanbnBoardPanel from './KanbnBoardPanel';
|
import KanbnBoardPanel from "./KanbnBoardPanel";
|
||||||
import KanbnBurndownPanel from './KanbnBurndownPanel';
|
import KanbnBurndownPanel from "./KanbnBurndownPanel";
|
||||||
import KanbnTaskPanel from './KanbnTaskPanel';
|
import KanbnTaskPanel from "./KanbnTaskPanel";
|
||||||
|
|
||||||
let kanbnStatusBarItem: KanbnStatusBarItem;
|
let kanbnStatusBarItem: KanbnStatusBarItem;
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext) {
|
export async function activate(context: vscode.ExtensionContext) {
|
||||||
|
|
||||||
// Register a command to initialise Kanbn in the current workspace. This command will be invoked when the status
|
// Register a command to initialise Kanbn in the current workspace. This command will be invoked when the status
|
||||||
// bar item is clicked in a workspace where Kanbn isn't already initialised.
|
// bar item is clicked in a workspace where Kanbn isn't already initialised.
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('kanbn.init', async () => {
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand("kanbn.init", async () => {
|
||||||
// If no workspace folder is opened, we can't initialise kanbn
|
// If no workspace folder is opened, we can't initialise kanbn
|
||||||
if (vscode.workspace.workspaceFolders === undefined) {
|
if (vscode.workspace.workspaceFolders === undefined) {
|
||||||
vscode.window.showErrorMessage('You need to open a workspace before initialising Kanbn.');
|
vscode.window.showErrorMessage("You need to open a workspace before initialising Kanbn.");
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Set the node process directory and import kanbn
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// If the input prompt wasn't cancelled, initialise kanbn
|
// Set the node process directory and import kanbn
|
||||||
if (newProjectName !== undefined) {
|
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
|
||||||
await kanbn.initialise({
|
const kanbn = await import("@basementuniverse/kanbn/src/main");
|
||||||
name: newProjectName
|
|
||||||
|
// 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();
|
// If the input prompt wasn't cancelled, initialise kanbn
|
||||||
}
|
if (newProjectName !== undefined) {
|
||||||
kanbnStatusBarItem.update();
|
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
|
// Register a command to open the kanbn board. This command will be invoked when the status bar item is clicked
|
||||||
// in a workspace where kanbn has already been initialised.
|
// in a workspace where kanbn has already been initialised.
|
||||||
context.subscriptions.push(vscode.commands.registerCommand('kanbn.board', async () => {
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand("kanbn.board", async () => {
|
||||||
|
// If no workspace folder is opened, we can't open the kanbn board
|
||||||
|
if (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
|
// Set the node process directory and import kanbn
|
||||||
if (vscode.workspace.workspaceFolders === undefined) {
|
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
|
||||||
vscode.window.showErrorMessage('You need to open a workspace before viewing the Kanbn board.');
|
const kanbn = await import("@basementuniverse/kanbn/src/main");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the node process directory and import kanbn
|
// If kanbn is initialised, view the kanbn board
|
||||||
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
|
if (await kanbn.initialised()) {
|
||||||
const kanbn = await import('@basementuniverse/kanbn/src/main');
|
KanbnBoardPanel.createOrShow(context.extensionPath, vscode.workspace.workspaceFolders[0].uri.fsPath, kanbn);
|
||||||
|
KanbnBoardPanel.update();
|
||||||
// If kanbn is initialised, view the kanbn board
|
} else {
|
||||||
if (await kanbn.initialised()) {
|
vscode.window.showErrorMessage("You need to initialise Kanbn before viewing the Kanbn board.");
|
||||||
KanbnBoardPanel.createOrShow(
|
}
|
||||||
context.extensionPath,
|
kanbnStatusBarItem.update();
|
||||||
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.
|
// 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
|
// Set the node process directory and import kanbn
|
||||||
if (vscode.workspace.workspaceFolders === undefined) {
|
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
|
||||||
vscode.window.showErrorMessage('You need to open a workspace before adding a new task.');
|
const kanbn = await import("@basementuniverse/kanbn/src/main");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the node process directory and import kanbn
|
// If kanbn is initialised, open the task webview
|
||||||
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
|
if (await kanbn.initialised()) {
|
||||||
const kanbn = await import('@basementuniverse/kanbn/src/main');
|
KanbnTaskPanel.show(context.extensionPath, vscode.workspace.workspaceFolders[0].uri.fsPath, kanbn, null, null);
|
||||||
|
} else {
|
||||||
// If kanbn is initialised, open the task webview
|
vscode.window.showErrorMessage("You need to initialise Kanbn before adding a new task.");
|
||||||
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.
|
// 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
|
// Set the node process directory and import kanbn
|
||||||
if (vscode.workspace.workspaceFolders === undefined) {
|
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
|
||||||
vscode.window.showErrorMessage('You need to open a workspace before viewing the burndown chart.');
|
const kanbn = await import("@basementuniverse/kanbn/src/main");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the node process directory and import kanbn
|
// If kanbn is initialised, view the burndown chart
|
||||||
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
|
if (await kanbn.initialised()) {
|
||||||
const kanbn = await import('@basementuniverse/kanbn/src/main');
|
KanbnBurndownPanel.show(context.extensionPath, vscode.workspace.workspaceFolders[0].uri.fsPath, kanbn);
|
||||||
|
KanbnBurndownPanel.updateAll();
|
||||||
// If kanbn is initialised, view the burndown chart
|
} else {
|
||||||
if (await kanbn.initialised()) {
|
vscode.window.showErrorMessage("You need to initialise Kanbn before viewing the burndown chart.");
|
||||||
KanbnBurndownPanel.createOrShow(
|
}
|
||||||
context.extensionPath,
|
kanbnStatusBarItem.update();
|
||||||
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 a workspace folder is open, add a status bar item and start watching for file changes
|
// If a workspace folder is open, add a status bar item and start watching for file changes
|
||||||
if (vscode.workspace.workspaceFolders !== undefined) {
|
if (vscode.workspace.workspaceFolders !== undefined) {
|
||||||
|
|
||||||
// Set the node process directory and import kanbn
|
// Set the node process directory and import kanbn
|
||||||
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
|
process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath);
|
||||||
const kanbn = await import('@basementuniverse/kanbn/src/main');
|
const kanbn = await import("@basementuniverse/kanbn/src/main");
|
||||||
|
|
||||||
// Create status bar item
|
// Create status bar item
|
||||||
kanbnStatusBarItem = new KanbnStatusBarItem(context, kanbn);
|
kanbnStatusBarItem = new KanbnStatusBarItem(context, kanbn);
|
||||||
@ -144,18 +132,18 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
// Initialise file watcher
|
// Initialise file watcher
|
||||||
const uri = vscode.workspace.workspaceFolders[0].uri.fsPath;
|
const uri = vscode.workspace.workspaceFolders[0].uri.fsPath;
|
||||||
const fileWatcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(
|
const fileWatcher = vscode.workspace.createFileSystemWatcher(
|
||||||
uri,
|
new vscode.RelativePattern(uri, `${kanbn.getFolderName()}/**.*`)
|
||||||
`${kanbn.getFolderName()}/**.*`
|
);
|
||||||
));
|
|
||||||
fileWatcher.onDidChange(() => {
|
fileWatcher.onDidChange(() => {
|
||||||
kanbnStatusBarItem.update();
|
kanbnStatusBarItem.update();
|
||||||
KanbnBoardPanel.update();
|
KanbnBoardPanel.update();
|
||||||
|
KanbnBurndownPanel.updateAll();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle configuration changes
|
// Handle configuration changes
|
||||||
vscode.workspace.onDidChangeConfiguration(e => {
|
vscode.workspace.onDidChangeConfiguration((e) => {
|
||||||
kanbnStatusBarItem.update();
|
kanbnStatusBarItem.update();
|
||||||
KanbnBoardPanel.update();
|
KanbnBoardPanel.update();
|
||||||
});
|
});
|
||||||
|
8
ext-src/getNonce.ts
Normal file
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: [] });
|
const [burndownData, setBurndownData] = useState({ series: [] });
|
||||||
|
|
||||||
window.addEventListener('message', event => {
|
window.addEventListener('message', event => {
|
||||||
const tasks = Object.fromEntries(event.data.tasks.map(task => [task.id, task]));
|
const tasks = event.data.tasks
|
||||||
|
? Object.fromEntries(event.data.tasks.map(task => [task.id, task]))
|
||||||
|
: {};
|
||||||
switch (event.data.type) {
|
switch (event.data.type) {
|
||||||
case 'index':
|
case 'index':
|
||||||
setName(event.data.index.name);
|
setName(event.data.index.name);
|
||||||
@ -66,7 +68,11 @@ function App() {
|
|||||||
case 'burndown':
|
case 'burndown':
|
||||||
setName(event.data.index.name);
|
setName(event.data.index.name);
|
||||||
setTasks(tasks);
|
setTasks(tasks);
|
||||||
setSprints(event.data.index.options.sprints || []);
|
setSprints(
|
||||||
|
'sprints' in event.data.index.options
|
||||||
|
? event.data.index.options.sprints
|
||||||
|
: []
|
||||||
|
);
|
||||||
setBurndownData(event.data.burndownData);
|
setBurndownData(event.data.burndownData);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -111,6 +117,7 @@ function App() {
|
|||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
sprints={sprints}
|
sprints={sprints}
|
||||||
burndownData={burndownData}
|
burndownData={burndownData}
|
||||||
|
vscode={vscode}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
100
src/Burndown.tsx
100
src/Burndown.tsx
@ -1,7 +1,9 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts';
|
import { ResponsiveContainer, LineChart, Line, CartesianGrid, XAxis, YAxis, Tooltip } from 'recharts';
|
||||||
|
import VSCodeApi from "./VSCodeApi";
|
||||||
|
import formatDate from 'dateformat';
|
||||||
|
|
||||||
const Burndown = ({ name, tasks, sprints, burndownData }: {
|
const Burndown = ({ name, tasks, sprints, burndownData, vscode }: {
|
||||||
name: string,
|
name: string,
|
||||||
tasks: Record<string, KanbnTask>,
|
tasks: Record<string, KanbnTask>,
|
||||||
sprints: KanbnSprint[],
|
sprints: KanbnSprint[],
|
||||||
@ -15,8 +17,14 @@ const Burndown = ({ name, tasks, sprints, burndownData }: {
|
|||||||
y: number
|
y: number
|
||||||
}>
|
}>
|
||||||
}>
|
}>
|
||||||
}
|
},
|
||||||
|
vscode: VSCodeApi
|
||||||
}) => {
|
}) => {
|
||||||
|
const [sprintMode, setSprintMode] = useState(sprints.length > 0);
|
||||||
|
const [sprint, setSprint] = useState(null);
|
||||||
|
const [startDate, setStartDate] = useState(null);
|
||||||
|
const [endDate, setEndDate] = useState(null);
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{name: 'one', uv: 20},
|
{name: 'one', uv: 20},
|
||||||
{name: 'two', uv: 80},
|
{name: 'two', uv: 80},
|
||||||
@ -26,14 +34,82 @@ const Burndown = ({ name, tasks, sprints, burndownData }: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div>burndown chart</div>
|
<div className="kanbn-header">
|
||||||
<LineChart width={600} height={300} data={data} margin={{ top: 5, right: 20, bottom: 5, left: 0 }}>
|
<h1 className="kanbn-header-name">
|
||||||
<Line type="monotone" dataKey="uv" stroke="#8884d8" />
|
<p>{name}</p>
|
||||||
<CartesianGrid stroke="#ccc" strokeDasharray="5 5" />
|
<div className="kanbn-burndown-settings">
|
||||||
<XAxis dataKey="name" />
|
<form>
|
||||||
<YAxis />
|
{
|
||||||
<Tooltip />
|
sprintMode
|
||||||
</LineChart>
|
? <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>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,7 @@ body.vscode-high-contrast {
|
|||||||
|
|
||||||
.kanbn-header-name p {
|
.kanbn-header-name p {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
flex: 1;
|
flex: 2;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
@ -42,6 +42,8 @@ body.vscode-high-contrast {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
background-color: var(--vscode-input-background);
|
background-color: var(--vscode-input-background);
|
||||||
color: var(--vscode-input-foreground);
|
color: var(--vscode-input-foreground);
|
||||||
|
font-family: var(--vscode-font-family);
|
||||||
|
font-size: var(--vscode-font-size);
|
||||||
border: 1px transparent solid;
|
border: 1px transparent solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,12 +342,13 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
|
|||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
background-color: var(--vscode-input-background);
|
background-color: var(--vscode-input-background);
|
||||||
color: var(--vscode-input-foreground);
|
color: var(--vscode-input-foreground);
|
||||||
|
font-family: var(--vscode-font-family);
|
||||||
|
font-size: var(--vscode-font-size);
|
||||||
border: 1px transparent solid;
|
border: 1px transparent solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanbn-task-editor-field-input[type=date] {
|
.kanbn-task-editor-field-input[type=date] {
|
||||||
font-family: var(--vscode-font-family);
|
padding: 6px 8px;
|
||||||
font-size: var(--vscode-font-size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanbn-task-editor-field-select {
|
.kanbn-task-editor-field-select {
|
||||||
@ -477,3 +480,60 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
|
|||||||
.kanbn-task-editor-field-comment-text .kanbn-task-editor-field-textarea {
|
.kanbn-task-editor-field-comment-text .kanbn-task-editor-field-textarea {
|
||||||
min-height: 90px;
|
min-height: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kanbn-burndown-settings {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-burndown-settings form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-burndown-settings-sprint-select {
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: var(--vscode-input-background);
|
||||||
|
color: var(--vscode-input-foreground);
|
||||||
|
border: 1px transparent solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-burndown-settings-input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
flex: 0.5;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: var(--vscode-input-background);
|
||||||
|
color: var(--vscode-input-foreground);
|
||||||
|
font-family: var(--vscode-font-family);
|
||||||
|
font-size: var(--vscode-font-size);
|
||||||
|
border: 1px transparent solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-burndown-settings-input[type=date] {
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-burndown-settings-sprint-select:hover,
|
||||||
|
.kanbn-burndown-settings-sprint-select:focus,
|
||||||
|
.kanbn-burndown-settings-input:hover,
|
||||||
|
.kanbn-burndown-settings-input:focus {
|
||||||
|
border-color: var(--vscode-input-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.vscode-dark .kanbn-burndown-settings-input[type="date"]::-webkit-calendar-picker-indicator {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-header-button-inactive {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-burndown {
|
||||||
|
height: 85vh;
|
||||||
|
width: 95vw;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user