import * as path from 'path'; import * as vscode from 'vscode'; import KanbnTaskPanel from './KanbnTaskPanel'; export default class KanbnBoardPanel { public static currentPanel: KanbnBoardPanel | 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 (KanbnBoardPanel.currentPanel) { KanbnBoardPanel.currentPanel._panel.reveal(column); } else { KanbnBoardPanel.currentPanel = new KanbnBoardPanel( extensionPath, workspacePath, column || vscode.ViewColumn.One, kanbn ); } } public static async update() { if (KanbnBoardPanel.currentPanel) { let index: any; try { index = await KanbnBoardPanel.currentPanel._kanbn.getIndex(); } catch (error) { vscode.window.showErrorMessage(error instanceof Error ? error.message : error); return; } let tasks: any[]; try { 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', 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') }); } } 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(KanbnBoardPanel.viewType, 'Kanbn Board', 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', '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._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; 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': // TODO open a burndown chart webview panel vscode.window.showErrorMessage('Not implemented yet!'); 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() { KanbnBoardPanel.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 `