Moving tasks

This commit is contained in:
Gordon 2021-03-29 03:47:32 +01:00
parent d2234485f4
commit fcb90c6e27
4 changed files with 153 additions and 68 deletions

View File

@ -8,43 +8,52 @@ export default class KanbnBoardPanel {
private readonly _panel: vscode.WebviewPanel; private readonly _panel: vscode.WebviewPanel;
private readonly _extensionPath: string; private readonly _extensionPath: string;
private readonly _kanbn: typeof import('@basementuniverse/kanbn/src/main');
private _disposables: vscode.Disposable[] = []; private _disposables: vscode.Disposable[] = [];
public static createOrShow(extensionPath: string) { public static createOrShow(
extensionPath: string,
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. // If we already have a panel, show it, otherwise create a new panel
// Otherwise, create a new panel.
if (KanbnBoardPanel.currentPanel) { if (KanbnBoardPanel.currentPanel) {
KanbnBoardPanel.currentPanel._panel.reveal(column); KanbnBoardPanel.currentPanel._panel.reveal(column);
} else { } else {
KanbnBoardPanel.currentPanel = new KanbnBoardPanel( KanbnBoardPanel.currentPanel = new KanbnBoardPanel(
extensionPath, extensionPath,
column || vscode.ViewColumn.One column || vscode.ViewColumn.One,
kanbn
); );
} }
} }
public static async updateBoard(kanbn: typeof import('@basementuniverse/kanbn/src/main')) { public static async update() {
if (KanbnBoardPanel.currentPanel) { if (KanbnBoardPanel.currentPanel) {
let index: any; let index: any;
try { try {
index = await kanbn.getIndex(); index = await KanbnBoardPanel.currentPanel._kanbn.getIndex();
} 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({
index, index,
tasks: (await kanbn.loadAllTrackedTasks(index)).map( tasks: (await KanbnBoardPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map(
task => kanbn.hydrateTask(index, task) task => KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task)
) )
}); });
} }
} }
private constructor(extensionPath: string, column: vscode.ViewColumn) { private constructor(
extensionPath: string,
column: vscode.ViewColumn,
kanbn: typeof import('@basementuniverse/kanbn/src/main')
) {
this._extensionPath = extensionPath; this._extensionPath = extensionPath;
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, {
@ -68,11 +77,42 @@ 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(message => { this._panel.webview.onDidReceiveMessage(async message => {
switch (message.command) { switch (message.command) {
// Display error message
case 'error': case 'error':
vscode.window.showErrorMessage(message.text); vscode.window.showErrorMessage(message.text);
return; return;
// Move a task
case 'kanbn.move':
try {
await kanbn.moveTask(message.task, message.column, message.position);
} catch (e) {
vscode.window.showErrorMessage(e.message);
}
return;
// Create a task
case 'kanbn.create':
//
return;
// Update a task
case 'kanbn.update':
//
return;
// Rename a task
case 'kanbn.rename':
//
return;
// Delete a task
case 'kanbn.delete':
//
return;
} }
}, null, this._disposables); }, null, this._disposables);
} }
@ -82,7 +122,6 @@ export default class KanbnBoardPanel {
// Clean up our resources // 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();
if (x) { if (x) {
@ -95,11 +134,12 @@ export default class KanbnBoardPanel {
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 scriptPathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', mainScript)); .file(path.join(this._extensionPath, 'build', mainScript))
const scriptUri = scriptPathOnDisk.with({ scheme: 'vscode-resource' }); .with({ scheme: 'vscode-resource' });
const stylePathOnDisk = vscode.Uri.file(path.join(this._extensionPath, 'build', mainStyle)); const styleUri = vscode.Uri
const styleUri = stylePathOnDisk.with({ scheme: 'vscode-resource' }); .file(path.join(this._extensionPath, 'build', mainStyle))
.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();

View File

@ -1,19 +1,24 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
export default class KanbnStatusBarItem { export default class KanbnStatusBarItem {
statusBarItem: vscode.StatusBarItem; private readonly _statusBarItem: vscode.StatusBarItem;
private readonly _kanbn: typeof import('@basementuniverse/kanbn/src/main');
constructor(context: vscode.ExtensionContext) { constructor(
this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0); context: vscode.ExtensionContext,
context.subscriptions.push(this.statusBarItem); kanbn: typeof import('@basementuniverse/kanbn/src/main')
) {
this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
context.subscriptions.push(this._statusBarItem);
this._kanbn = kanbn;
} }
async update(kanbn: typeof import('@basementuniverse/kanbn/src/main')): Promise<void> { async update(): Promise<void> {
if (this.statusBarItem === undefined) { if (this._statusBarItem === undefined) {
return; return;
} }
if (await kanbn.initialised()) { if (await this._kanbn.initialised()) {
const status = await kanbn.status(true); const status = await this._kanbn.status(true);
const text = [ const text = [
`$(project) ${status.tasks}` `$(project) ${status.tasks}`
]; ];
@ -28,14 +33,14 @@ export default class KanbnStatusBarItem {
text.push(`$(check) ${status.completedTasks}`); text.push(`$(check) ${status.completedTasks}`);
tooltip.push(`${status.completedTasks} completed task${status.completedTasks === 1 ? '' : 's'}`); tooltip.push(`${status.completedTasks} completed task${status.completedTasks === 1 ? '' : 's'}`);
} }
this.statusBarItem.text = text.join(' '); this._statusBarItem.text = text.join(' ');
this.statusBarItem.tooltip = tooltip.join('\n'); this._statusBarItem.tooltip = tooltip.join('\n');
this.statusBarItem.command = 'kanbn.board'; this._statusBarItem.command = 'kanbn.board';
} else { } else {
this.statusBarItem.text = '$(project) Not initialised'; this._statusBarItem.text = '$(project) Not initialised';
this.statusBarItem.tooltip = 'Click to initialise'; this._statusBarItem.tooltip = 'Click to initialise';
this.statusBarItem.command = 'kanbn.init'; this._statusBarItem.command = 'kanbn.init';
} }
this.statusBarItem.show(); this._statusBarItem.show();
} }
} }

View File

@ -41,8 +41,8 @@ export async function activate(context: vscode.ExtensionContext) {
name: newProjectName name: newProjectName
}); });
vscode.window.showInformationMessage(`Initialised kanbn project '${newProjectName}'.`); vscode.window.showInformationMessage(`Initialised kanbn project '${newProjectName}'.`);
kanbnStatusBarItem.update(kanbn); kanbnStatusBarItem.update();
KanbnBoardPanel.updateBoard(kanbn); KanbnBoardPanel.update();
} }
})); }));
@ -62,8 +62,8 @@ export async function activate(context: vscode.ExtensionContext) {
// If kanbn is initialised, view the kanbn board // If kanbn is initialised, view the kanbn board
if (await kanbn.initialised()) { if (await kanbn.initialised()) {
KanbnBoardPanel.createOrShow(context.extensionPath); KanbnBoardPanel.createOrShow(context.extensionPath, kanbn);
KanbnBoardPanel.updateBoard(kanbn); KanbnBoardPanel.update();
} else { } else {
vscode.window.showErrorMessage('You need to initialise kanbn before viewing the kanbn board.'); vscode.window.showErrorMessage('You need to initialise kanbn before viewing the kanbn board.');
} }
@ -77,16 +77,16 @@ export async function activate(context: vscode.ExtensionContext) {
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); kanbnStatusBarItem = new KanbnStatusBarItem(context, kanbn);
kanbnStatusBarItem.update(kanbn); kanbnStatusBarItem.update();
KanbnBoardPanel.updateBoard(kanbn); KanbnBoardPanel.update();
// 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(uri, '.kanbn/*')); const fileWatcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '.kanbn/*'));
fileWatcher.onDidChange(() => { fileWatcher.onDidChange(() => {
kanbnStatusBarItem.update(kanbn); kanbnStatusBarItem.update();
KanbnBoardPanel.updateBoard(kanbn); KanbnBoardPanel.update();
}); });
} }
} }

View File

@ -3,54 +3,94 @@ import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
export type task = { export type task = {
id: string, id: string,
name: string name: string,
} description: string,
column: string,
workload?: number,
remainingWorkload?: number,
progress?: number,
metadata: {
created: Date,
updated?: Date,
completed?: Date,
assigned?: string
},
relations: Array<{
type: string,
task: string
}>,
subTasks: Array<{
text: string,
completed: boolean
}>,
comments: Array<{
author: string,
date: Date,
text: string
}>
};
declare var acquireVsCodeApi: any;
const vscode = acquireVsCodeApi();
const onDragEnd = (result, columns, setColumns) => { const onDragEnd = (result, columns, setColumns) => {
if (!result.destination) return;
// No destination means the item was dragged to an invalid location
if (!result.destination) {
return;
}
// Get the source and destination columns
const { source, destination } = result; const { source, destination } = result;
// The item that was moved
let removed: task;
// The task was dragged from one column to another
if (source.droppableId !== destination.droppableId) { if (source.droppableId !== destination.droppableId) {
const sourceColumn = columns[source.droppableId]; const sourceItems = columns[source.droppableId];
const destColumn = columns[destination.droppableId]; const destItems = columns[destination.droppableId];
const sourceItems = [...sourceColumn.items]; [removed] = sourceItems.splice(source.index, 1);
const destItems = [...destColumn.items];
const [removed] = sourceItems.splice(source.index, 1);
destItems.splice(destination.index, 0, removed); destItems.splice(destination.index, 0, removed);
setColumns({ setColumns({
...columns, ...columns,
[source.droppableId]: { [source.droppableId]: sourceItems,
...sourceColumn, [destination.droppableId]: destItems
items: sourceItems
},
[destination.droppableId]: {
...destColumn,
items: destItems
}
}); });
// The task was dragged into the same column
} else { } else {
const column = columns[source.droppableId];
const copiedItems = [...column.items]; // If the task was dragged to the same position that it currently occupies, don't move it (this will
const [removed] = copiedItems.splice(source.index, 1); // prevent unnecessarily setting the task's updated date)
if (source.index === destination.index) {
return;
}
const copiedItems = columns[source.droppableId];
[removed] = copiedItems.splice(source.index, 1);
copiedItems.splice(destination.index, 0, removed); copiedItems.splice(destination.index, 0, removed);
setColumns({ setColumns({
...columns, ...columns,
[source.droppableId]: { [source.droppableId]: copiedItems
...column,
items: copiedItems
}
}); });
} }
// Post a message back to the extension so we can move the task in the index
vscode.postMessage({
command: 'kanbn.move',
task: removed.id,
column: destination.droppableId,
position: destination.index
});
}; };
const Board = ({ columns }) => { const Board = ({ columns }) => {
// const [columns, setColumns] = useState(props.columns); const [, setColumns] = useState(columns);
return ( return (
<div style={{ display: "flex", justifyContent: "center", height: "100%" }}> <div style={{ display: "flex", justifyContent: "center", height: "100%" }}>
{/* <DragDropContext <DragDropContext
onDragEnd={result => onDragEnd(result, columns, setColumns)} onDragEnd={result => onDragEnd(result, columns, setColumns)}
> */} >
<DragDropContext>
{Object.entries(columns).map(([columnId, column]) => { {Object.entries(columns).map(([columnId, column]) => {
return ( return (
<div <div
@ -122,6 +162,6 @@ const Board = ({ columns }) => {
</DragDropContext> </DragDropContext>
</div> </div>
); );
} };
export default Board; export default Board;