diff --git a/docs/styles.md b/docs/styles.md new file mode 100644 index 0000000..2e81b2b --- /dev/null +++ b/docs/styles.md @@ -0,0 +1,41 @@ +# Styles + +The kanbn board has a default style which is based on the current vscode theme, however this can be overridden by creating a CSS file `board.css` inside the `.kanbn/` directory. + +## CSS class structure + +### Kanbn board + +- `div.kanbn-header` + - `h1.kanbn-header-name` + - `p.kanbn-header-description` +- `div.kanbn-board` + - `div.kanbn-column.kanbn-column-{column name in snakecase}` + - `h2.kanbn-column-name` + - `i.codicon.codicon-{chevron-right or check}` + - `span.kanbn-column-count` + - `button.kanbn-create-task-button` + - `i.codicon.codicon-add` + - `div.kanbn-column-task-list[.drag-over when dragging a task over this column]` + - `div.kanbn-task[.drag when being dragged]` + - `div.kanbn-task-row` + - `button.kanbn-task-name` + - `div.kanbn-task-row` + - `div.kanbn-task-data.kanbn-task-tags` + - `span.kanbn-task-tag.kanbn-task-tag-{tag name in snakecase}` + - `div.kanbn-task-row` + - `div.kanbn-task-data.kanbn-task-assigned` + - `i.codicon.codicon-account` + - `div.kanbn-task-data.kanbn-task-date` + - `i.codicon.codicon-clock` + - `div.kanbn-task-data.kanbn-task-comments` + - `i.codicon.codicon-comment` + - `div.kanbn-task-data.kanbn-task-sub-tasks` + - `i.codicon.codicon-tasklist` + - `div.kanbn-task-data.kanbn-task-workload` + - `i.codicon.codicon-run` + - `div.kanbn-task-progress` + +### Task editor + +- `// TODO` diff --git a/docs/test-themes.md b/docs/test-themes.md new file mode 100644 index 0000000..b47bd2a --- /dev/null +++ b/docs/test-themes.md @@ -0,0 +1,10 @@ +Tested with the following themes: +https://vscodethemes.com/e/Yummygum.city-lights-theme +https://vscodethemes.com/e/Luxcium.pop-n-lock-theme-vscode +https://vscodethemes.com/e/wwmyers.hackpot +https://vscodethemes.com/e/selfrefactor.Niketa-theme +https://vscodethemes.com/e/liviuschera.noctis +https://vscodethemes.com/e/johnpapa.winteriscoming +https://vscodethemes.com/e/Gyunbie.high-contrast-yellow-theme +https://vscodethemes.com/e/high-contrast-light.high-contrast-light +https://vscodethemes.com/e/danibram.theme-clear diff --git a/ext-src/KanbnBoardPanel.ts b/ext-src/KanbnBoardPanel.ts index 04f4343..1f991d6 100644 --- a/ext-src/KanbnBoardPanel.ts +++ b/ext-src/KanbnBoardPanel.ts @@ -126,7 +126,7 @@ export default class KanbnBoardPanel { return; // Create a task - case 'kanbn.create': + case 'kanbn.addTask': KanbnTaskPanel.show( this._extensionPath, this._workspacePath, diff --git a/ext-src/KanbnTaskPanel.ts b/ext-src/KanbnTaskPanel.ts index e6e1a7c..9dc829e 100644 --- a/ext-src/KanbnTaskPanel.ts +++ b/ext-src/KanbnTaskPanel.ts @@ -18,9 +18,30 @@ export default class KanbnTaskPanel { workspacePath: string, kanbn: typeof import('@basementuniverse/kanbn/src/main'), taskId: string|null, - columnName: string + columnName: string|null ) { const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined; + let index: any; + try { + index = await kanbn.getIndex(); + } catch (error) { + vscode.window.showErrorMessage(error instanceof Error ? error.message : error); + return; + } + let task: any = null; + if (taskId) { + try { + task = await kanbn.hydrateTask(index, await kanbn.getTask(taskId)); + } catch (error) { + vscode.window.showErrorMessage(error instanceof Error ? error.message : error); + return; + } + } + + // If no columnName is specified, use the first column + if (!columnName) { + columnName = Object.keys(index.columns)[0]; + } // Create a new panel const taskPanel = new KanbnTaskPanel( @@ -33,24 +54,10 @@ export default class KanbnTaskPanel { ); KanbnTaskPanel.panels.push(taskPanel); - let index: any; - try { - index = await kanbn.getIndex(); - } catch (error) { - vscode.window.showErrorMessage(error instanceof Error ? error.message : error); - return; - } - let task: any = null; - if (taskId) { - try { - task = await kanbn.getTask(taskId); - } catch (error) { - vscode.window.showErrorMessage(error instanceof Error ? error.message : error); - return; - } - } + // Send task data to the webview taskPanel._panel.webview.postMessage({ type: 'task', + index, task, columnName: taskPanel._columnName, tasks: await kanbn.loadAllTrackedTasks(index), @@ -78,7 +85,7 @@ export default class KanbnTaskPanel { enableScripts: true, // Retain state even when hidden - // retainContextWhenHidden: true, + retainContextWhenHidden: true, // Restrict the webview to only loading content from allowed paths localResourceRoots: [ @@ -111,16 +118,27 @@ export default class KanbnTaskPanel { vscode.window.showErrorMessage(message.text); return; + // Update the task webview panel title + case 'kanbn.updatePanelTitle': + this._panel.title = message.title; + return; + + // Create a task + case 'kanbn.create': + // TODO create task + vscode.window.showInformationMessage('create task'); + return; + // Update a task case 'kanbn.update': // TODO update task - vscode.window.showInformationMessage(`Updating task ${message.taskId}`); + vscode.window.showInformationMessage('update task'); return; // Delete a task case 'kanbn.delete': // TODO delete task - vscode.window.showInformationMessage(`Deleting task ${message.taskId}`); + vscode.window.showInformationMessage('delete task'); return; } }, null, this._disposables); diff --git a/ext-src/extension.ts b/ext-src/extension.ts index 0042764..7697bf9 100644 --- a/ext-src/extension.ts +++ b/ext-src/extension.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import KanbnStatusBarItem from './KanbnStatusBarItem'; import KanbnBoardPanel from './KanbnBoardPanel'; +import KanbnTaskPanel from './KanbnTaskPanel'; let kanbnStatusBarItem: KanbnStatusBarItem; @@ -73,6 +74,33 @@ export async function activate(context: vscode.ExtensionContext) { } })); + // Register a command to add a new kanbn task. + 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; + } + + // 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 initialised, open the task webview + 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.'); + } + })); + // If a workspace folder is open, add a status bar item and start watching for file changes if (vscode.workspace.workspaceFolders !== undefined) { diff --git a/package-lock.json b/package-lock.json index 32dc493..73b34fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3361,6 +3361,12 @@ "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", "dev": true }, + "@types/lodash": { + "version": "4.14.168", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", + "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==", + "dev": true + }, "@types/node": { "version": "10.17.56", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.56.tgz", @@ -9019,6 +9025,11 @@ } } }, + "fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=" + }, "fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -9686,6 +9697,46 @@ "assert-plus": "^1.0.0" } }, + "git-config-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-1.0.1.tgz", + "integrity": "sha1-bTP37WPbDQ4RgTFQO6s6ykfVRmQ=", + "requires": { + "extend-shallow": "^2.0.1", + "fs-exists-sync": "^0.1.0", + "homedir-polyfill": "^1.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "git-user-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-user-name/-/git-user-name-2.0.0.tgz", + "integrity": "sha512-1DC8rUNm2I5V9v4eIpK6PSjKCp9bI0t6Wl05WSk+xEMS8GhR8GWzxM3aGZfPrfuqEfWxSbui5/pQJryJFXqCzQ==", + "requires": { + "extend-shallow": "^2.0.1", + "git-config-path": "^1.0.1", + "parse-git-config": "^1.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -10048,6 +10099,14 @@ "os-tmpdir": "^1.0.1" } }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -10642,10 +10701,9 @@ "dev": true }, "immer": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", - "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==", - "dev": true + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.1.tgz", + "integrity": "sha512-7CCw1DSgr8kKYXTYOI1qMM/f5qxT5vIVMeGLDCDX8CSxsggr1Sjdoha4OhsP0AZ1UvWbyZlILHvLjaynuu02Mg==" }, "import-cwd": { "version": "2.1.0", @@ -10725,8 +10783,7 @@ "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { "version": "6.5.2", @@ -10965,8 +11022,7 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { "version": "1.0.0", @@ -12186,8 +12242,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -13403,6 +13458,27 @@ "safe-buffer": "^5.1.1" } }, + "parse-git-config": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-1.1.1.tgz", + "integrity": "sha1-06mYQxcTL1c5hxK7pDjhKVkN34w=", + "requires": { + "extend-shallow": "^2.0.1", + "fs-exists-sync": "^0.1.0", + "git-config-path": "^1.0.1", + "ini": "^1.3.4" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "parse-glob": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", @@ -13425,6 +13501,11 @@ "json-parse-better-errors": "^1.0.1" } }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, "parse5": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", @@ -16602,6 +16683,12 @@ "locate-path": "^3.0.0" } }, + "immer": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", + "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==", + "dev": true + }, "inquirer": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", diff --git a/package.json b/package.json index 8c1a1ad..4e2bf9f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,11 @@ "command": "kanbn.board", "title": "Open board", "category": "Kanbn" + }, + { + "command": "kanbn.addTask", + "title": "Add task", + "category": "Kanbn" } ] }, @@ -27,6 +32,9 @@ "@basementuniverse/kanbn": "file:~/Projects/kanbn", "@types/dateformat": "^3.0.1", "dateformat": "^4.5.1", + "git-user-name": "^2.0.0", + "immer": "^9.0.1", + "lodash": "^4.17.21", "param-case": "^3.0.4", "react": "^16.3.2", "react-beautiful-dnd": "12.2.0", @@ -45,6 +53,7 @@ }, "devDependencies": { "@types/jest": "^23.3.14", + "@types/lodash": "^4.14.168", "@types/node": "^10.17.56", "@types/react": "^16.14.5", "@types/react-dom": "^16.9.12", diff --git a/src/App.tsx b/src/App.tsx index 33a7154..2a81b6f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ function App() { const [dateFormat, setDateFormat] = useState(''); const [task, setTask] = useState({}); const [columnName, setColumnName] = useState(''); + const [columnNames, setColumnNames] = useState([] as string[]); window.addEventListener('message', event => { switch (event.data.type) { @@ -39,6 +40,7 @@ function App() { case 'task': setTask(event.data.task); setColumnName(event.data.columnName); + setColumnNames(Object.keys(event.data.index.columns)); break; } setType(event.data.type); @@ -46,7 +48,7 @@ function App() { }); return ( -