Split out into separate classes

This commit is contained in:
Gordon 2021-03-27 14:02:00 +00:00
parent 40632935ee
commit 878fcbba39
7 changed files with 257 additions and 195 deletions

View File

@ -1,36 +1,42 @@
import * as path from 'path';
import * as vscode from 'vscode';
export default class ReactPanel {
public static currentPanel: ReactPanel | undefined;
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 _disposables: vscode.Disposable[] = [];
public static createOrShow(extensionPath: string) {
public static createOrShow(extensionPath: string, workspacePath: string) {
const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined;
// If we already have a panel, show it.
// Otherwise, create a new panel.
if (ReactPanel.currentPanel) {
ReactPanel.currentPanel._panel.reveal(column);
if (KanbnBoardPanel.currentPanel) {
KanbnBoardPanel.currentPanel._panel.reveal(column);
} else {
ReactPanel.currentPanel = new ReactPanel(extensionPath, column || vscode.ViewColumn.One);
KanbnBoardPanel.currentPanel = new KanbnBoardPanel(
extensionPath,
workspacePath,
column || vscode.ViewColumn.One
);
}
}
private constructor(extensionPath: string, column: vscode.ViewColumn) {
private constructor(extensionPath: string, workspacePath: string, column: vscode.ViewColumn) {
this._extensionPath = extensionPath;
this._workspacePath = workspacePath;
// Create and show a new webview panel
this._panel = vscode.window.createWebviewPanel(ReactPanel.viewType, "React", column, {
this._panel = vscode.window.createWebviewPanel(KanbnBoardPanel.viewType, "Kanbn Board", column, {
// Enable javascript in the webview
enableScripts: true,
// And restric the webview to only loading content from our extension's `media` directory.
// Restrict the webview to only loading content from our extension's `media` directory.
localResourceRoots: [
vscode.Uri.file(path.join(this._extensionPath, 'build'))
]
@ -46,19 +52,15 @@ export default class ReactPanel {
// Handle messages from the webview
this._panel.webview.onDidReceiveMessage(message => {
switch (message.command) {
case 'alert':
case 'error':
vscode.window.showErrorMessage(message.text);
return;
}
}, null, this._disposables);
}
public doRefactor() {
this._panel.webview.postMessage({ command: 'refactor' });
}
public dispose() {
ReactPanel.currentPanel = undefined;
KanbnBoardPanel.currentPanel = undefined;
// Clean up our resources
this._panel.dispose();
@ -90,16 +92,14 @@ export default class ReactPanel {
<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>React App</title>
<title>Kanbn Board</title>
<link rel="stylesheet" type="text/css" href="${styleUri}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src vscode-resource: https:; script-src 'nonce-${nonce}';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>`;

View File

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

View File

@ -1,7 +1,8 @@
import * as vscode from 'vscode';
import ReactPanel from './ReactPanel';
import KanbnStatusBarItem from './KanbnStatusBarItem';
import KanbnBoardPanel from './KanbnBoardPanel';
let statusBarItem: vscode.StatusBarItem;
let kanbnStatusBarItem: KanbnStatusBarItem;
export async function activate(context: vscode.ExtensionContext) {
@ -40,7 +41,7 @@ export async function activate(context: vscode.ExtensionContext) {
name: newProjectName
});
vscode.window.showInformationMessage(`Initialised kanbn project '${newProjectName}'.`);
updateStatusBarItem(kanbn);
kanbnStatusBarItem.update(kanbn);
}
}));
@ -60,7 +61,7 @@ export async function activate(context: vscode.ExtensionContext) {
// If kanbn is initialised, view the kanbn board
if (await kanbn.initialised()) {
ReactPanel.createOrShow(context.extensionPath);
KanbnBoardPanel.createOrShow(context.extensionPath, kanbn.getMainFolder());
} else {
vscode.window.showErrorMessage('You need to initialise kanbn before viewing the kanbn board.');
}
@ -74,36 +75,15 @@ export async function activate(context: vscode.ExtensionContext) {
const kanbn = await import('@basementuniverse/kanbn/src/main');
// Create status bar item
statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0);
context.subscriptions.push(statusBarItem);
updateStatusBarItem(kanbn);
kanbnStatusBarItem = new KanbnStatusBarItem(context);
kanbnStatusBarItem.update(kanbn);
// Initialise file watcher
const uri = vscode.workspace.workspaceFolders[0].uri.fsPath;
const fileWatcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '.kanbn/*'));
fileWatcher.onDidChange(() => {
updateStatusBarItem(kanbn);
// TODO update kanbn board
kanbnStatusBarItem.update(kanbn);
// TODO refresh kanbn board
});
}
}
export function deactivate(): void {
//
}
async function updateStatusBarItem(kanbn: typeof import('@basementuniverse/kanbn/src/main')): Promise<void> {
if (statusBarItem === undefined) {
return;
}
const initialised = await kanbn.initialised();
const text = initialised ? await getStatusBarText(kanbn) : 'Not initialised';
statusBarItem.text = `$(project) ${text}`;
statusBarItem.command = initialised ? 'kanbn.board' : 'kanbn.init';
statusBarItem.show();
}
async function getStatusBarText(kanbn: typeof import('@basementuniverse/kanbn/src/main')): Promise<string> {
const status = await kanbn.status(true);
return Object.values(status.columnTasks).join(' / ');
}

View File

@ -1,9 +0,0 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

View File

@ -1,147 +1,37 @@
import React, { useState } from "react";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
import uuid from "uuid/v4";
// import * as vscode from 'vscode';
import Board from './Board';
import Header from './Header';
import React, { useEffect, useState } from "react";
const itemsFromBackend = [
{ id: uuid(), content: "First task" },
{ id: uuid(), content: "Second task" },
{ id: uuid(), content: "Third task" },
{ id: uuid(), content: "Fourth task" },
{ id: uuid(), content: "Fifth task" }
];
function App(props) {
// const [cwd, setCwd] = useState('initialcwd');
// const [index, setIndex] = useState({});
// const [tasks, setTasks] = useState([]);
const columnsFromBackend = {
[uuid()]: {
name: "Requested",
items: itemsFromBackend
},
[uuid()]: {
name: "To do",
items: []
},
[uuid()]: {
name: "In Progress",
items: []
},
[uuid()]: {
name: "Done",
items: []
}
};
// useEffect(() => {
// // process.chdir(vscode!.workspace.workspaceFolders[0].uri.fsPath);
// import('@basementuniverse/kanbn/src/main').then(kanbn => {
// vscode.postMessage({
// command: 'error',
// text: 'hello!'
// });
// });
// });
// console.log(vscode!.workspace.workspaceFolders[0].uri.fsPath);
const onDragEnd = (result, columns, setColumns) => {
if (!result.destination) return;
const { source, destination } = result;
// window.addEventListener('message', event => {
// const message = event.data; // The JSON data our extension sent
// switch (message.command) {
// case 'test':
// setCwd(message.cwd);
// break;
// }
// });
if (source.droppableId !== destination.droppableId) {
const sourceColumn = columns[source.droppableId];
const destColumn = columns[destination.droppableId];
const sourceItems = [...sourceColumn.items];
const destItems = [...destColumn.items];
const [removed] = sourceItems.splice(source.index, 1);
destItems.splice(destination.index, 0, removed);
setColumns({
...columns,
[source.droppableId]: {
...sourceColumn,
items: sourceItems
},
[destination.droppableId]: {
...destColumn,
items: destItems
}
});
} else {
const column = columns[source.droppableId];
const copiedItems = [...column.items];
const [removed] = copiedItems.splice(source.index, 1);
copiedItems.splice(destination.index, 0, removed);
setColumns({
...columns,
[source.droppableId]: {
...column,
items: copiedItems
}
});
}
};
function App() {
const [columns, setColumns] = useState(columnsFromBackend);
return (
<div style={{ display: "flex", justifyContent: "center", height: "100%" }}>
<DragDropContext
onDragEnd={result => onDragEnd(result, columns, setColumns)}
>
{Object.entries(columns).map(([columnId, column], index) => {
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center"
}}
key={columnId}
>
<h2>{column.name}</h2>
<div style={{ margin: 8 }}>
<Droppable droppableId={columnId} key={columnId}>
{(provided, snapshot) => {
return (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{
background: snapshot.isDraggingOver
? "lightblue"
: "lightgrey",
padding: 4,
width: 250,
minHeight: 500
}}
>
{column.items.map((item, index) => {
return (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
>
{(provided, snapshot) => {
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
userSelect: "none",
padding: 16,
margin: "0 0 8px 0",
minHeight: "50px",
backgroundColor: snapshot.isDragging
? "#263B4A"
: "#456C86",
color: "white",
...provided.draggableProps.style
}}
>
{item.content}
</div>
);
}}
</Draggable>
);
})}
{provided.placeholder}
</div>
);
}}
</Droppable>
</div>
</div>
);
})}
</DragDropContext>
<div>
<Header />
<Board />
</div>
);
}

149
src/Board.tsx Normal file
View File

@ -0,0 +1,149 @@
import React, { useState } from "react";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
import uuid from "uuid/v4";
const itemsFromBackend = [
{ id: uuid(), content: "First task" },
{ id: uuid(), content: "Second task" },
{ id: uuid(), content: "Third task" },
{ id: uuid(), content: "Fourth task" },
{ id: uuid(), content: "Fifth task" }
];
const columnsFromBackend = {
[uuid()]: {
name: "Requested",
items: itemsFromBackend
},
[uuid()]: {
name: "To do",
items: []
},
[uuid()]: {
name: "In Progress",
items: []
},
[uuid()]: {
name: "Done",
items: []
}
};
const onDragEnd = (result, columns, setColumns) => {
if (!result.destination) return;
const { source, destination } = result;
if (source.droppableId !== destination.droppableId) {
const sourceColumn = columns[source.droppableId];
const destColumn = columns[destination.droppableId];
const sourceItems = [...sourceColumn.items];
const destItems = [...destColumn.items];
const [removed] = sourceItems.splice(source.index, 1);
destItems.splice(destination.index, 0, removed);
setColumns({
...columns,
[source.droppableId]: {
...sourceColumn,
items: sourceItems
},
[destination.droppableId]: {
...destColumn,
items: destItems
}
});
} else {
const column = columns[source.droppableId];
const copiedItems = [...column.items];
const [removed] = copiedItems.splice(source.index, 1);
copiedItems.splice(destination.index, 0, removed);
setColumns({
...columns,
[source.droppableId]: {
...column,
items: copiedItems
}
});
}
};
function Board() {
const [columns, setColumns] = useState(columnsFromBackend);
return (
<div style={{ display: "flex", justifyContent: "center", height: "100%" }}>
<DragDropContext
onDragEnd={result => onDragEnd(result, columns, setColumns)}
>
{Object.entries(columns).map(([columnId, column], index) => {
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center"
}}
key={columnId}
>
<h2>{column.name}</h2>
<div style={{ margin: 8 }}>
<Droppable droppableId={columnId} key={columnId}>
{(provided, snapshot) => {
return (
<div
{...provided.droppableProps}
ref={provided.innerRef}
style={{
background: snapshot.isDraggingOver
? "lightblue"
: "lightgrey",
padding: 4,
width: 250,
minHeight: 500
}}
>
{column.items.map((item, index) => {
return (
<Draggable
key={item.id}
draggableId={item.id}
index={index}
>
{(provided, snapshot) => {
return (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
userSelect: "none",
padding: 16,
margin: "0 0 8px 0",
minHeight: "50px",
backgroundColor: snapshot.isDragging
? "#263B4A"
: "#456C86",
color: "white",
...provided.draggableProps.style
}}
>
{item.content}
</div>
);
}}
</Draggable>
);
})}
{provided.placeholder}
</div>
);
}}
</Droppable>
</div>
</div>
);
})}
</DragDropContext>
</div>
);
}
export default Board;

11
src/Header.tsx Normal file
View File

@ -0,0 +1,11 @@
import React, { useState } from "react";
function Header() {
return (
<div>
Header
</div>
);
}
export default Header;